I originally wrote the following as an internal corporate blog post to guide a pair of business analysts responsible for writing and unit testing business rules. The advice below applies pretty well to software testing in general.
80% of your test scenarios should cover failure cases, with the other 20% covering success cases. Too much of testing (unit testing or otherwise) seems to cover the happy path. A 4:1 ratio of failure case tests to success case tests will result in more durable software.
Given a range of valid values for an input, the following tests are strongly recommended:
- Test of behavior at minimum value in range
- Test of behavior at maximum value in range
- Tests outside of valid value range
- Below minimum value
- Above maximum value
- Test of behavior within the range
The following tests roughly conform to the 80/20 rule, and apply to numeric values, dates and times.
Above and beyond the boundary/range testing described above, the testing of dates creates a need to test how code handles different orderings of those values relative to each other. For example, if a method has a start and end date as inputs, you should test to make sure that the code responds with some sort of error if the start date is later than the end date. If a method has start and end times as inputs for the same day, the code should respond with an error if the start time is later than the end time. Testing of date or date/time-sensitive code must include an abstraction to represent current date and time as a value (or values) you choose, rather than the current system date and time. Otherwise, you’ll have no way to test code that should only be executed years in the future.
Given that a boolean value is either true or false, testing code that takes a boolean as an input seems quite simple. But if a method has multiple inputs that can be true or false, testing that the right behavior occurs for every possible combination of those values becomes less trivial. Combine that with the possibility of a null value, or multiple null values being provided (as described in the next section) and comprehensive testing of a method with boolean inputs becomes even harder.
It is very important to test how a method behaves when it receives null values instead of valid data. The method under test should fail in graceful way instead of crashing or displaying cryptic error messages to the user.
Arrange-Act-Assert is the organizing principle to follow when developing unit tests. Arrange refers to the work your test should do first in order to set up any necessary data, creation of supporting objects, etc. Act refers to executing the scenario you wish to test. Assert refers to verifying that the outcome you expect is the same as the actual outcome. A test should have just one assert. The rationale for this relates to the Single Responsibility Principle. That principles states that a class should have one, and only one, reason to change. As I apply that to testing, a unit test should test only one thing so that the reason for failure is clear if and when that happens as a result of subsequent code changes. This approach implies a large number of small, targeted tests, the majority of which should cover failure scenarios as indicated by the 80/20 Rule defined earlier.
Test-First Development & Refactoring
This approach to development is best visually explained by this diagram. The key thing to understand is that a test that fails must be written before the code that makes the test pass. This approach ensures that test is good enough to catch any failures introduced by subsequent code changes. This approach applies not just to new development, but to refactoring as well. This means, if you plan to make a change that you know will result in broken tests, break the tests first. This way, when your changes are complete, the tests will be green again and you’ll know your work is done. You can find an excellent blog post on the subject of test-driven development by Bob Martin here.
I first learned about Arrange-Act-Assert for unit test organization from reading The Art of Unit Testing by Roy Osherove. He’s on Twitter as @RoyOsherove. While it’s not just about testing, Clean Code (by Bob Martin) is one of those books you should own and read regularly if you make your living writing software.