Regardless of the type of test the purposes usually discussed for units tests include
- Verifying the code works
- Providing 'documentation' as to what is meant to happen
- Making it easier to refactor
I believe all of these benefits can occur - but I don't think unit testing alone delivers any of them. As with everything the business value of the tests need to be measured. A common mistake is to spend so long building testing frameworks not enough work is done. It is often cheaper to use manual testing for GUI code.
It is also key to remember that have your subsystems operate as you intended is NOT a business goal. The business goal is to have the GUI operate and apply their business rules. Obsessive unit testing can often obscure the fact the system 'works' but it doesn't actually deliver what the business wanted.
Common Issues
What is the correct level to test
Often I hear there are too many, or too few unit tests for a particular bit of the system. When there are too many the overhead of maintaining the tests becomes so great changes become harder, when too few we don't know the system works.
As with everything it comes back to a useful level of tests for the business need. There is no point writing units tests to check your code can add 1+ 1 and get 2 - if that doesn't work you may as well give up. There is also no point in writing tests that are so fragile that the slightest GUI change breaks them. This sort of thing is cheaper to test with manual regression scripts.
Always consider
- Time to write the test
- How fragile is it
- How much of the system is this actually testing
- How must of the system is this testing that won't be tested by other tests that HAVE to happen anyhow
If a piece of code can be tested cheaper by a higher level test - then use that. Testing is not a religion - it is a process to help deliver systems.
What about data?
Admin data is the weak point of all attempts at Unit testing. In general any code that does (non trivial) work talks to external systems, databases, message queues etc. These have the annoying feature that they maintain state making it hard to produce re-producable unit tests.
There are 3 main strategies to deal with this
Mock Objects
Using a framework (or manually) you create a 'fake' database service that looks to the class being tested as if it is the real thing. This is a 'simple' implementation that returns the same sequence of data.
Pros
- The core class can be tested
Cons
- The mock object can become very complex
- It reduces the value of the test case - as you are only testing the 'easy' code in your class above the 'hard' integration layer
Read-only data
If you are lucky enough to integrate to systems/databases on a readonly basis then simply define a set of known data and test against that.
Pros
- Tests the full DB layer
- Makes it easy to test the full stack
Cons
- Not always possible
- Can have issues with time sensitive data
Create data as you go
The final option is to create all the data you need as you go. e.g. you need a user, so you just create one.
Pros
- Tests the full DB layer
- Makes it easy to test the full stack
Cons
- Leaves a lot of items behind in the database
Generally I use a combination of these approaches as appropriate.