Monday, January 17, 2011

Unit Testing - the Good

Automated Unit Tests are a big part of the Agile movement. One of the reasons is that refactoring is a big part of Agile. Any time you change code, you risk breaking it. Unit tests mitigate that risk, allowing you to refactor with less fear. The ability to easily verify that you haven't broken things when you refactor code or do bug fixes or other maintenance is probably the biggest gain from creating automated unit tests, but it isn't the only one. Besides increased confidence, unit tests can sometimes actually decrease development time in the short term and also provide long term documentation of your software.

Development Time
When you write code how do you know when it is right? There are different levels of tested that you are probably familiar with.

 0. The code is written
 1. The code compiles
 2. The code runs
 3. You've tested the code

Ever fixed a small typo in your code and then just checked it in? Yep, that's level 0 testing. Level 1 testing (also known as proof by compilation) is more common, especially if you are using an IDE like Eclipse that does constant compiling. Level 2 testing is smoke testing - will the program still run? Unfortunately, some times these levels aren't enough. Whether it is pressure from customers or a boss, or just pesky pride, you actually want to validate that the logic in the code is correct. This requires level 3 testing.

The most obvious way to test is to run your program over and over again, providing inputs that exercise your newly changed code. This can seem easier than writing unit tests but is it really?

If your application is something large, or has external dependencies, each run can take a long time. If your new code isn't perfect the first time through, the "code - compile - deploy - execute - enter test data - validate results" development cycle can be a very slow way of debugging. Writing the unit tests has a higher up front cost, but now the debug cycle has been reduced to "code - compile - execute tests".  Which means that unless you write your code perfectly the first time, it can actually be quicker to write unit tests than not.

Unit Tests provide a great example of how to use a component. Let's say you have a method that formats phone numbers in a canonical way. Reading the unit tests can show you how different addresses get formatted. And unlike almost all other forms of software documentation, it is never out of date. As long as the unit tests pass you know that this "documentation" is still accurate.

As I stated in the introduction, the most obvious benefit of unit testing is confidence. It gives you confidence that the code you've written is correct as you write it. If you create unit tests based on bug reports that come in, you can be confident that they have both been fixed and that the bugs won't reappear in the future. If you are maintaining code with existing unit tests, you can have confidence that your changes aren't breaking other assumptions you may not have been aware of.

Here is a simple unit testing example of a class that formats phone numbers that provides a concrete example of the three ideas from above:
  1 import static org.junit.Assert.*;
  2 import org.junit.Test; 
  3 import org.junit.Before; 
  5 public class PhoneFormatterTest {
  7   private PhoneFormatter mFormatter; 
  9   @Before public void setup() {
 10     mFormatter = new PhoneFormatter(); 
 11   } 
 13   @Test public void testDirectNumber() {
 14     assertEquals("(757) 555-1234", mFormatter.format("7575551234")); 
 15     assertEquals("(757) 555-1234", mFormatter.format("17575551234")); 
 16     assertEquals("555-1234", mFormatter.format("5551234")); 
 17     assertEquals("911", mFormatter.format("911")); 
 18   } 
 20   @Test public void testDialNine() {
 21     assertEquals("(757) 555-1234", mFormatter.format("97575551234")); 
 22     assertEquals("(757) 555-1234", mFormatter.format("917575551234")); 
 23     assertEquals("911", mFormatter.format("9911")); 
 24   } 
 25 }
Having a unit test like this really helps with the development time. Just seeing how the different numbers are supposed to be formatted can help see what you need to do. And it'll be quicker to run the unit test once than to manually test your code with the different types of numbers. It gives the documentation for future developers who can see how the numbers are supposed to be formatted - i.e. what the format method does. And it provides confidence that your code not only handles all of these situations right now, but it still will after it is modified at some future date.

1 comment:

Jennifer said...

Again, good common sense advice like the refactoring post. But convincing a time-crunched boss that one needs extra time to do this now for benefits that will not be seen until later can be tough sometimes. Like refactoring, I've learned it is best not to ask, but just to do it.