Principles of Software Testing
Posted: 06/23/09 2:50 PM
A few months ago I did lunch-n-learn at work around testing. I wrote down a number of principles that were useful to keep in mind when thinking about when writing automated tests. I figured I would share.
- Testing as Safeguard. The basic, most obvious idea behind testing. Rapid change in code should bring fear. Test to reduce fear.
- Testing as Proof. Tests can be a good way to prove to you and your peers that your code doesn’t suck.
- Your Code Should Be As Independent as Possible I (Separation of Concerns). Code that is in the BL should not be concerned with Html, the HttpRequest object and such things. The UI should not be concerned with the DL. Make each area concerned only with its own matters as much as possible.
- Your Code Should Be As Independent as Possible II (Dependency Injection & Mocking). It is impossible to remove all dependencies from your code. However, the fewer dependencies and places of tight coupling, the easier code is to test. A popular and effective way to reduce dependency is to follow the pattern of dependency injection where you supply to the code being called its external dependency. Mocking also helps in this regard, as it allows you to handle dependencies in tests more easily, and gives you fantastic control over the values returned from typically-dependent code.
- Testing Can Help Design (TDD). Making code testable from the start will almost always lead to better design because it practically forces the separation of concerns and helps you focus on the big picture of the functionality and not the details of its implementation.
- Testing as Documentation. Test code is some of the best documentation you can have. It not only explains how to use code, but because it is always being run it is kept up-to-date. This is rarely the case with external documentation.
- Do Not Use Shared Mutable Dependencies. If two tests share the same thing, and that thing can change, your tests are likely invalid and can lead to inconsistent results. Unless the resources you are using are static, create what you need for your test each time.
- Express Test Intent with the Name. Don’t name your test something like “Test1”. Use an expressive name so you can glance at the test and know what it covers.
- Testing Allows More Parallel Development. Want to run your code and get working when the database guys aren’t finished, you don’t have the final documentation on an api you will be using, or the code you are calling doesn’t work? Use mock objects to give yourself something to program against.
- Avoid Static and Sealed. Static and sealed classes and members cannot be overridden. This gets in the way of testing. There are times for these things, but if it ever needs to be switched out for mocking or dependency injection, you are out of luck.
- The Test Must Pass Consistently. Inconsistently passing tests usually do so because they are using shared data or resources with other tests. Isolate tests from each other. Never check in a test if it occassionally fails. Otherwise you never know when you have a real problem.
- Expression of Intent versus Actual Execution. Sometimes it is a lot easier to test something if it returns an expression of intent rather than actually executing an action. For example, the RedirectResult in ASP.NET MVC. You cannot for a unit test test an actual redirect easily, but you can test if a RedirectResult is returned from an action. This can also help with achieving a separation of concerns, as it might be better to execute that action elsewhere.
- Test as You Go. If you do all of your coding and then stop to test, testing is often very tedious and boring. If you test as you go it is a lot more interesting. So test as you develop as it will help your design (see TDD above) and seem like less of a chore.