Introduction to Unit Testing
Unit Testing Explained
Unit testing is done by developers, for other developers.
A unit test is a piece of code written by a developer that exercises a very small, specific area of functionality in the code being tested. The purpose is to prove that the code does what it is intended to do. By building up confidence in individual pieces of code, we can proceed to test the entire working application.
Unit tests will make for better designed code and drastically reduce the amount of time spent debugging code. Furthermore, unit tests help communicate the code's intended use, showing how the code is expected to behave under various conditions.
Unit tests should be organized around behaviours, and not necessarily around individual methods.
Effective unit tests have the following properties:
- Automatic - Invoking units tests and checking the results should happen automatically.
- Thorough - Test code must be thorough in that it must test everything interesting about a behaviour that might break. If it's not likely to contain a bug, don't bother testing it.
- Repeatable - Every test should be able to run over and over again, in any order, and produce the same results.
- Independent - Tests should be tightly focused, and independent from the environment and each other. Only test one thing at a time. Should be able to run any individual test at any time, and in any order. Don't want to have to rely on any other test having run first.
- Professional - Test must be written and maintained to the same professional standards as your production code.
There are specific areas that need to be tested:
- Validate the results of the test - Simply to see if the expected results are correct.
- Identify application boundary conditions - This is where most bugs generally reside.
- Does the value conform to an expected format?
- Is the set of values ordered or unordered as appropriate?
- Is the value within reasonable minimum and maximum values?
- Does the code reference anything external that isn't under direct control of the code itself?
- Does the value exist (e.g. is non-null, nonzero, present)?
- Are there exactly enough values?
- Is everything happening in order? At the right time? In time?
- Some methods can be checked by applying their logical inverse to see if you get back to an original state.
- You might also be able to cross-check results of your method using different means.
- Test that your code handles real world problems by forcing errors to occur.
- Test performance characteristics to ensure that the performance curve remains stable.
Where to Put Test Code
- Option 1 - The first and easiest method of structuring test code is to simply include it right in the same project and assembly alongside the production code. This has the advantage that test classes can access internal and protected internal member variables and methods. But the disadvantage is that the test code is lying around, cluttering up the production code directory.
- Option 2 - Another method is to create your tests in a separate assembly from the production code. This has the advantage of keeping a clean separation between code that you ship and code for testing. The disadvantage is that now the test code is in a different assembly; You won't be able to access internal or protected internal members unless your test code uses a subclass of the production code that exposes the necessary members. You then use the subclass in the test code instead of using base class directly.