The term Unit Testing is now widely used in almost every organization that I work with. That is great, now all we (as an industry) need to do is educate these organization on what the term actually means. Most project teams that I work with understand unit testing as just general testing, and there is no defining concept or application associated with Unit Testing. Now I am sure there are many definitions of what Unit Testing is.
In my opinion, Unit Testing is ensuring each method and properly of a class (or unit) functions and fails as expected. What was that? Did you say “fails”? Yes, you need to ensure that expected failures occur when you pass invalid data. This is just as important, and sometimes more important that ensuring a function executes as expected when you pass valid data.
This means that a complete set of Unit Tests would test each class in each component and each tier of you application. You do not just test the presentation tiers public methods and assume all underlying code is implicitly tested.
It is extremely important to make each test completely atomic in nature. This means that the success of a unit test must not depend on the execution of other unit tests. It also means that unit test should not be dependent on functionality provided by other classes & tiers. This requirement can be very difficult to achieve in some cases. You should create mock objects to provide known input and output from associated functions.
I find it very helpful to write my tests before I implement the method being tested. The process I generally follow is to stub out the class with all the necessary properties and methods. This defines the interface to the class that needs to be tested. I do not implement any of the properties or methods but I do through a NotImplemented exception and add a TODO comment. With this done I create the necessary test cases to assert valid and invalid calls. At this point all the tests should fail, if any of them pass you have a problem. Now you implement each class and refractor until all the tests pass. You might discover new test cases as you are implementing the class, make sure you take the time to add these unit tests as you discover them.
This process provides the following benefits:
You know when you are done
If you make changes to an aspect of the application you can immediately discover if you have effected another area of the application
When you make changes to a class you know if you inadvertently changed the business logic
Another good practice around unit testing lies with bug resolution. As bugs are reported you should write a unit test to reproduce the bug. Once completed the test will fail and you work on the issue until the test passes. Of course you need to re-run all the other unit tests as well to ensure you have not created another issue with your fix. As an added bonus you also now have a test to ensure that the bug you just found and resolved can not reappear at a later date. You have a test for it.
All this may sound like a great deal of work but if you institute and enforce the process before the first line of code is written the task never becomes overwhelming. The benefits and return on investment here are exponential.