Unit Tests
How would you define Unit Tests?
That’s not a rhetorical question: I am asking (and perhaps challenging) you to come up with a crisp definition of a Unit Test. A definition that is short, is not circular, and does not need to refer to a specific programming language or unit-testing framework.
If you find that hard, you are in good company. “Like most software development terminology, [unit testing] is very ill defined.”
My colleague, James Saffell, recently gave a talk on “Consulting, Communication, and You”. In it, he provided a definition of unit-testing that he had heard from a technologist at a client:
Unit tests are tests that validate the assumptions made by the developer who is writing the code
The technologist had used this definition to claim that unit tests weren’t necessary, as “all they did was reflect the assumptions of the developer writing the tests themselves”.
I think that it is an excellent definition. However in marked contrast with the technologist who coined it, I think it justifies why you should do as much of your testing doing automated unit tests as possible.
Imagine that you’re writing a web application for a small-yet-growing online retailer that does business in the United States. (Not the most original example, but bear with me!) It is likely that you’ll have to get the customer’s shipping and billing addresses at some point. It is also likely that the shipping rates to Alaska and Hawaii would be different from the remaining 48 states, because of geography.
Let’s say that as a small company, you can currently bill customers living in Alaska or Hawaii; however, you’re unable to ship your merchandize to these states because the higher costs don’t yet fit into your business model. All of this creates interesting scenarios. The delivery team discusses these scenarios and gets to work. Imagine that the developer responsible for the checkout feature writes these unit tests:
it "should allow all 50 states while entering billing address" do ... end it "should allow contiguous 48 states while entering shipping address" do ... end
These unit tests represent requirements directly, so you could say that no explicit assumptions have been made by the developer thus far.
Let’s assume that the developer implements the shipping address list by removing “AK” and “HI” from the 50 states. It achieves the desired result. The developer, being diligent and well-versed in the art of testing, writes this additional unit test:
it "removes AK and HI from all states to get list of shippable states" do ... end
While she’s at it, the developer also writes a test validating that all state abbreviations are two letters long:
it "should abbreviate state names to 2-letter abbreviations" do ... end
You build and deploy your software and life is fine. Until someone in marketing remembers Puerto Rico, Guam, and US Virgin Islands.
You decide that you want to expand your business to allow customers with billing addresses in these areas. You still cannot ship to these US territories, however, you decide that it is a valid compromise for your growing business.
Your team gets together to deliver this new feature: billing addressess should now show “PR”, “GU”, and “US VI” in addition to the 50 states. You use “US VI” to ensure that your users clearly understand your inability to accept “British Virgin Islands” as part of a billing address.
Aren’t you glad now that your developer wrote all those unit tests validating her assumptions!
As part of implementing this new feature, the team will quickly discover – a failing test will remind them – that it’s no longer correct to only remove AK and HI from the list of acceptabile billing states to get shipping states. They will also discover that not all state name abbreviations are two letters long. Those are the obvious and direct benefits of the unsolicited unit tests the diligent developer wrote.
There are other, subtle yet equally important benefits. In fixing all these unit tests, the developers feel unease at writing something like this:
it "should abbreviate state names to 2-5 letter abbreviations" do ... end
They realize their unease comes from the fact that Puerto Rico, Guam and US Virgin Island are not states. They see that all throughout the code, these entities are named “state”. When they look further and talk to the whole team, the architect, the business analyst, and the two test engineers confirm that they refer to these places as “states” in their artifacts, too. This starts a conversation about the validity of the Domain Model. The team realizes that the collective assumption they made about naming the geographical units within the United States is incorrect. After a bit of research, they agree on the term “Territory” to represent all fifty states and territories. They recognize that this is a better term when the company expands operations to Canada (Provinces and Territories) and Mexico (Estados). As a team, they decide to make changes in the software, documentation, and other artifacts to reflect this. Most importantly, they change their daily vocabulary to use “Territories” to refer to these places.
Unit tests are indeed testable assertions about the assumptions that developers make. That’s why you should insist on having them as executable code.
Image credit: @essenj