Unit Testing: A Short Primer

I realize that I am not the first to write about unit testing, but there seems to to be an absence of examples dealing with only simple unit testing.  Most all documents deal with unit testing as part of an overall strategy like XP or agile, I don’t really care when you do it as long as you do.

Example Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ExampleClass {
 
	private String message;
 
	public String getMessage() {
		return this.message;
	}
 
	public void setMessage(String message) {
		this.message = message;
	}
 
	public String operation() {
		StringBuffer buf = new StringBuffer();
		if(message.length() > 10) {
			buf.append(message.hashCode());
		} else {
			buf.append(doubleMessage());
		}
 
		return buf.toString();
	}
 
	private String doubleMessage() {
		return message + message;
	}
 
}

Now this is a stupid class that doesn’t really do anything but since we’ve written it we need to make sure that it works.

First we start with a new test class:

1
2
3
public class ExampleClassTest extends TestCase {
 
}

Of course this class doesn’t actually do anything, but it does show us that it is the Test for ExampleClass based on its name, and that it extends TestCase. Now lets add a test method:

1
2
3
4
5
6
7
8
9
10
public class ExampleClassTest extends TestCase {
 
	public void testMessageStoring() throws Exception {
		ExampleClass example = new ExampleClass();
		example.setMessage('test');
 
		assertEquals('test', example.getMessage());
	}
 
}

As you can see all we are doing is invoking a method on the class and asserting that it responds in an appropriate way, in this case when I set a string that should be available via the get method.  All tests are of the format ‘public void test
() throws Exception { }’,  and junit will be able to locate this test method and run it.  But this test isn’t very comprehensive because it only executes 10 out ot the possible 45 instructions in our class, so we will need to do more testing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ExampleClassTest extends TestCase {
 
	public void testMessageStoring() throws Exception {
		ExampleClass example = new ExampleClass();
		example.setMessage('test');
 
		assertEquals('test', example.getMessage());
	}
 
	public void testClassOperation() throws Exception {
		ExampleClass example = new ExampleClass();
		example.setMessage('test');
 
		assertEquals('testtest', example.operation());
	}
 
}

Here we’ve added a method that tests the operation method of our class, but we know there are two paths inside the operation method and we aren’t exercising 100% of the class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ExampleClassTest extends TestCase {
 
	public void testMessageStoring() throws Exception {
		ExampleClass example = new ExampleClass();
		example.setMessage('test');
 
		assertEquals('test', example.getMessage());
	}
 
	public void testClassOperation() throws Exception {
		ExampleClass example = new ExampleClass();
		example.setMessage('test');
 
		assertEquals('testtest', example.operation());
	}
 
	public void testClassOperationLongMessage() throws Exception {
		ExampleClass example = new ExampleClass();
		example.setMessage('testalongermessage');
 
		assertEquals('-1184182769', example.operation());
	}
 
}

Now we have a test that covers 100% of the instructions in the our class. Now I know that this is a trivial example but unit testing you code shouldn’t be much harder than this, if it is then it is probably a good sign that the complexity of your class has grown beyond what it should be.



AccuRev: Considered Harmful

OK maybe that’s over stating it a little bit, its not quite as bad as ‘goto’.

I’ve now been using AccuRev for about 6 months now, and I it seems that I am having to adjust my process to suit some of the idiosyncrasies of the tools. Now I’m not going to say that its a bad tool, because as with everything in computer science I’m certain that there was a specific use case that was identified that AccuRev fills perfectly, but I don’t have that use-case.

My primary gripe is the inability to update my local workspace while I have a dirty workspace. Now while I understand the rational for this, it irritates me to no end. When my team is working toward a release we are all working very closely and when somebody else completes a feature that I would find useful I would like to be able to checkout their changes without having to commit my changes. The AccuRev team seems to think that taking a course view of solves many of the SCM problems, and indeed it probably does .. for the tool designers, but what about the tool users?

In any decent software project, the software will naturally fallout into different modules, and I find it extremely useful to sometimes update a piece of the tree without updating other pieces. AccuRev doesn’t allow this at all, it forces you to update everything or nothing. This is the fundamental cornerstone of my problem with this product.  With any tool I expect to have some level of choice in how I will use that tool, and I fully understand that I am accepting the responsibility to utilize that tool properly else I only have myself to blame when damage is caused. In AccuRev everything is very one-dimensional and doesn’t allow me to work the way I want to work, and really who is supposed to be in control here, the human or the tool? (hint if your tool is AccuRev then you are not in charge)

Then there is this business with server workspaces, in AccuRev there are two levels for checking in a file, there is a ‘keep’ which stores the change in your private workspace on the server and then there is a ‘promote’ which puts the revision in the public stream to be shared with everybody else. The main problem with the approach is mostly psychological, having these two levels actually lengthens development iterations because people can check into their private workspaces for the duration of the development effort (I’ve seen weeks and months) and then only promote and merge into the public stream when they are completely finished, its very waterfall and not really helping anybody because it lengthens the integration and doesn’t provide the rest of your team with the benefits of your work until its too late. Now I will admit that this is a process problem, but it is something to consider because if you are working with inexperienced developers this might go unnoticed and then everybody will be in for a surprise when a very large merge happens and nothing works anymore.

At the end of the day AccuRev is a inflexible tool that comes with a pretty hefty price tag. I would rather use a free tool like subversion any day. While I’m certain that most of my complaints are petty, when you use a tool everyday its the little things that start to eat away at you. While I’m sure some of my frustrations are my own fault, I’ve tried to get to the bottom of all of them and have found no research to make me think otherwise.



Findbugs

findbugs

I will go on record saying that I love static analysis tools, especially findbugs. Now I’m not saying that they can work miracles, just like any tool they need to be used in conjunction with a somewhat functioning human brain in order to do more good than harm, but the thing that I like the most about the this tool in particular is that it is so easy.  Much like the tag line for the project “Findbugs: because it’s easy” suggests, the software is very simple to run and will quickly provide useful and sometime life saving (I guess this depends on what your software does) results.  Each bug that is found is identified with a bug description, a severity, a line number, and on request a detailed description of why what you wrote is less that ideal and suggestions for pattens or more proper methods to employ.  It basically does everything short of fixing it for you, how cool is that.  Of course there is a catch, findbugs can and will produce some false positives for bugs.  However in my view I see this as a half blessing, because it forces you to take a look at something that you probably knew to be dubious while writing and will either shame you into using a less questionable method or at least let you convince yourself that this is exactly what you want.

The other hurdle that a first time findbugs user will find, especially when running it against a large project, is that he/she will be overwhelmed by the number of bugs that they find.  Now lets be clear, nobody is suggesting that you sit down in an afternoon and fix all of them, one of the interesting things about static analysis is that it doesn’t actually know the execution path of your code.  So it reports on all the possible paths that it figure out, and while it is probably correct that the code is full of bugs the module may be tested well enough for you to have confidence in the way that you are using it anyway.  My rule of thumb is that I will run the tool and make myself away of the bugs and then as I go about my normal day adding features and closing trouble tickets I will fix the bugs that are in the classes that I touch, always leaving it better than I found it.

Eclipse Plugin

Now before when I said it was easy I ment it, but since they made an eclipse plugin if you are not using this tool you are coding irresponsibly.  Once you install the plugin and restart eclipse all you have to do is right click on the root of your project and say ‘Findbugs -> findbugs’ and it will go out and do its thing, reporting everythign in the console with all other compiler errors and warnings.

Continuous Integration

The other thing that I am a big fan of is letting findbugs (and other tools like PMD, jDepend, etc..) run on your continous integration server of choice.  Findbugs comes with a set of ant tasks that integrates nicely into many existing build suites.  Most of these tools have findbugs plugins (I know hudson does for sure) and allow you to moniter the number of bugs being reported for each checkin.  This will allow you and your team to see the result of their work, if the number goes up then they should know to go back and run the tool and fix their bugs, and if the number goes down they should feel good about themselves so having averted a potential disaster (at least thats how you can sell it to your boss at review time).  The beuty of running it on a continous integration server is that you don’t even need to remember to do it, and yet you can still reap the benefits of its insight.

Bravo findbugs, bravo.



Who cares about style?

I do. Its weird the things that irritate you, but bad style is quickly becoming a top ten pet-peeve. Code style is something that is easily taken for granted and you never fully appreciate it until its gone. I’ve been fortunate to have worked on many projects over the past few years where basic style conventions were enforced, and not by tools such as checkstyle or PMD, but by an actual human.

Using tools to aid in enforcing style can help, but they end up being pretty poor at the end of the day at guaranteeing that you will have nice readable/understandable code. As code bases grow very large, it becomes evermore important that we help our brains navigate quickly and the use of proper style sends subtle signals about the flow of a class. When I see camel-case starting with a lowercase letter I know its a method, uppercase and I know its a constructor.

Strangely enough I am not actually calling for people to follow ‘all’ of the rules of a style guide just rules like the following:

  • packages contain lowercase letters only
  • classes start with an uppercase letter
  • Constructors start with an uppercase letter (this is a freebie if you follow above)
  • methods start with lowercase letters
  • code blocking such as if/for/while/do always use braces

Following these rules should be pretty easy, actually it should be almost no work at all and yet it will pay huge dividends when your coworkers (or you after a few days) attempt to read through your code.  I know that this is merely a small fraction of the rules of any real style guide, but I think if everybody did just these few things we would be well on our way to coding utopia.