In Alan Cooper's The Inmates Are Running the Asylum, he poses a question: What do you get when you cross a warship with a computer? In the example he provides, the USS Yorktown was conducting fleet maneuvers in the Atlantic; a Navy technician was calibrating an on-board fuel valve and entered a zero into one of the shipboard management computers. A program attempted to divide another number by the entered zero (which is mathematically undefined), and bam! It caused a complete crash of the entire control system, which resulted in the ship sitting dead in the water for hours until it could be towed back to shore.
It seems unlikely the ship's entire management system was wholly untested before it was installed and given some sort of test drive. This scenario screams, "Really? You didn't catch that?" What if the ship had been engaged in hostile action when the fated zero was entered?
While Inmates tackles overall design, testing is the focus of this column. Dividing by zero? That is an obvious dirty test on whatever function processed the value and choked. Supposing some form of unit testing had been at the forefront, it is smart conjecture this critical failure in calculation would have been caught very early on.
Unit testing is employed for that very reason. As defined on MSDN, the primary goal of unit testing is to take the smallest piece of testable software in the application, isolate it from the remainder of the code, and determine whether it behaves exactly as you expect. As a whole and as a tenet of Test Driven Development (TDD), unit testing has proven its value in that a large percentage of defects are identified during its use and early on in the development process.
Properties of a good unit test
The most common approach to unit testing requires drivers and stubs to be written. The driver simulates a calling unit and the stub simulates the called unit. In Roy Osherove's The Art of Unit Testing, the first chapter outlines the properties of a good unit test:
- It should be automated and repeatable.
- It should be easy to implement.
- Once it's written, it should remain for future use.
- Anyone should be able to run it.
- It should run at the push of a button.
- It should run quickly.
If you're still unclear how to begin writing unit tests, ask yourself the following questions about the tests you've written up to now:
- Can I run and get results from a unit test I wrote two weeks or two months or two years ago?
- Can any member of my team run and get the results from unit tests I wrote two months ago?
- Can I run all the unit tests I've written in no more than a few minutes?
- Can I run all the unit tests I've written at the push of a button?
- Can I write a basic unit test in no more than a few minutes?
If you answered 'no' to any of these questions, there's a high probability that you are not implementing a unit test.
The importance of code design and unit tests
To implement unit tests, code must be written in a manner that supports it. To use unit tests, you are forced to evaluate how you write your code and how to make it more singular-accessible. If, for instance, you have one spaghetti function that manages to hit a handful of other areas or functions, writing a unit test for that particular function is not really going to yield much value. Sure, you could write a unit test for it, but what would you really gain from the test result? It would not give individual units of isolated code feedback.Writing good unit tests
In his Writing Good Unit Tests blog post, Steve Sanderson offers great guidance on how to write good unit tests. I've summarized some of his instructions below:
- Make each test orthagonal to all of the others. Any given behavior should be specified in one and only one test:
a. Don't make unnecessary assertions (or have only one logical assertion per test).
b. Test only one unit of code at a time.
c. Use mocking for all external services or state.d. Avoid unnecessary preconditions.
- Do not unit test configuration settings. Configuration settings are just that - configurations; the settings are not active units of code.
- Name your unit tests clearly and consistently.
In one of my favorite books, Beautiful Architecture: Leading Thinkers Reveal the Hidden Beauty in Software Design, there's a case study of an application called Design Town. Decisions were made in the beginning that shaped the successful implementation, maintenance, and development of the application. It is stressed that unit tests were important in reinforcing good coding practices and keeping the application's architecture strong.
By making the decision up-front to employ unit tests, developing with unit tests all but replaces doing arduous code reviews.
While it certainly fits well with TDD, the decision to use unit tests should not depend on whether you are using this development methodology. The sole purpose of testing anything is to answer the question: How do you make sure that your application (or onboard ship control system) works as you intended? Unit tests allow you to answer this question at the lowest level.
- Deborah Kurata's introduction to unit testing
- Pragmatic Unit Testing in C# with NUnit, 2nd Edition (Hunt, Thomas)
- Visual Studio 2008 offers developers more testing options