Java, Web and Mobile


Agreements under Test – Part II


Now that I managed to gain some experience with automatic future proof testing of requirements I take the next level: combining my skills of writing parameterized tests with syntactical agreements. I’m fond of patterns and static code analysis. But a list of >10k warnings in an IDE is not an argument for sales department so I add unit tests breaking the build if someone (including me) shows bad habits improving the sources.

So the first and easy to adapt pattern is a Java Bean. According to the specs it needs a pair of get- and set-methods, a public constructor with no arguments and what is put into by the set method is returned with get. Three simple tests and my skills from above yield a test like the following:

With the help of a little reflection API as well as some of the included Java Bean tools from the JDK (Introspector) I get access to the internals to add matchers. The set-get-call-chain is for the creative developers. It needs a few factories to instantiate the various types beyond primitives and String but it works out as a powerful tool. I like to combine this with fuzzy testing and stuff arbitrary data into import interfaces or renderers, filters and such – double and triple carnage for the developer I proved witlessly. As a side note why I prefer assertThat(…, empty()) over immediate failure: as with the example from the first part, collecting all the non-unique IDs from enum values the tests return all the methods that fail in get-set-pairing.

Such tests become more important, when the agreements become more subtle: Each Set- or Collection-type must be unmodifiable for example. In my case having the tests from above quite easy: The test code puts in a non empty Set or Collection derived from the factory. It calls the read method provided through the PropertyDescriptor, gets the iterator and invokes remove. An exception should occur. That can be checked with the help of an ExpectedException-rule (see JUnit’s JavaDoc again for @Rule resp. ExpectedException). No need for any developer to think about the reason. A test fails, he looks it up and my test method explains: »If the list were modifiable, how would the notification about changes be implemented? Stop others from manipulating your private parts without your approval.«

I turned that test class from above into an abstract one, made it a template and provide it through the testutils. All that is necessary is to extend from it, add a proper @Parameters-method and return the list of classes to be checked. The next section shows this for another silent agreement.

Another design element is an immutable class. I use them quite often because they are inherently thread safe. (If in doubt, read Brian Goetz’ Java Concurrency in Practice.) First I added the annotation @Immutable and decorated some classes with it. As said before there could be a template. It will check that there are no set-methods and all attributes declared final. Again a factory is necessary to instantiate the immutables, because there is no public no-args-constructor. Easy to write down now:

Some may ask: why the annotations? Why not? They represent an aspect and maybe I get the working time granted to write an annotation processor. This processor would create the tests automatically. No need to write anything anymore. Until then I have syntactical elements that explain my intention. Annotations can carry a JavaDoc. I can search all the references in my workspace and it simply looks good if I can write something like: @Immutable implies @ThreadSafe. A few may reckon this annotation style from Brian Goetz’ Concurrency In Practice, another showcase for the importance of patterns and annotations.

Since the tests were introduced re-occuring build breakers disappeared. Some were minor issues like missing translation others runtime failures in system testing with non-unique error codes.  Developers now find the meaning beyond the technical and business logic aspects expressed in the code. And if they miss it, e.g. introduce a set-method in one of the @Immutable classes, their unit test run points out the issue. The code is also on the safe side when changing fundamental non functional requirements, at least the ones expressed as unit test.