Wednesday, September 28, 2011

Unit Testing Java beans with CGLIB and Builders

Unit testing of methods that modify variables of java beans is a fairly common task that tends to be fairly tedious to do. Worse, it tends to not test all the variables of the java beans for hidden side effects. This tends to cause hidden bugs and makes refactoring difficult to do.

Problems with testing java beans:
  1. Setting up beans to pass into methods is repetitive and can be many lines long since you can not chain your methods together (aka you can not do a.setB().setC().. etc).
  2. Testing all of the properties of the bean can be even more painful as to do it correctly you must test every property of the bean and you can't rely on an equals method to be there and worse, you can't rely on the equals method being reliable.
  3. Reflection can be used but will slow down your unit tests if used pervasively throughout your system

Luckily, with a little bit of code, some runtime bytecode generation and the help of an Eclipse plugin, things are much easier to do correctly, more concise and quite quick.

Short version:
CGLIB helper methods and examples
Eclipse Builder Pattern plugin

Long version:
Let's start with a very simple java bean and a method that modifies it:


And a standard unit test that passes, but doesn't quite test everything


For those paying close attention, the updateZipCodes method is setting 2 fields on the User object, while the unit test is only testing one of them. This is a very common bug in test code and almost impossible to find. Code coverage tools would give you 100% test coverage on that test and I do not know of any tools that would find this problem. While in this particular instance, it is fairly easy to see the issue, in real world examples, it can be far more difficult.

The first step to making this easier to create and easier to read is to create a builder for the java bean. To do this easily, I used an eclipse plugin I found at http://code.google.com/p/bpep/. Simply drop the jar file in your eclipse dropins directory and restart eclipse. Then right click on the java bean, and Source->Generate Builder Pattern Code->Generate. This will generate a builder inside your bean that looks like:


Now, we update our unit test to use the new Builder class we just generated:


This version is quite a bit faster to type as you can change all your setters together, and takes up a bit less space than the previous version. It still fails to find the modification though. To use this, we use CGLIBs BeanMap to compare all the properties. CGLIB is a bytecode generation library that generates code at runtime. While it is perhaps best know for helping generate annotations or method wrappers, it also has some useful fast versions of reflection.  The BeanMap class represents any java bean as a Map of getters and setters.  Behind the scenes, a dynamically generated class is created at runtime that allows the normal map operations, of which get, set and entrySet are the most useful.  This class has very good performance, signficantly faster than Apache's BeanUtils class. Using this class, we simply create an expected data object, and then compare the 2 data objects as if they were maps.

Here is our new generic assert method:


And here is the modified unit test to use it


Notice that our code now checks all of the properties of the bean and is easy to read and understand. The same techniques can be used for any method that creates a bean or modify a bean. The only issue, is how to easily handle beans that are in some third party library that you can't generate builders for. There is also the issue of having to manually create all of your builders by hand. CGLIB can also be used here, as we can generate our builder with cglib itself as well


And now our test looks like:


I personally prefer generating the builders so that auto complete works properly but there are certainly benefits to not cluttering up the code base. I did put a little bit of effort into attempting to generate the builders as a step in the compile phase but I decided against it as being too complicated.

As mentioned above, all of the source and the test cases, along with a bonus BeanMapParser for parsing simple lines of text can be found at github.

No comments:

Post a Comment