According to Wikipedia, unit testing is a method by which individual units of source code (…) are tested to determine whether they are fit for use. “Fit for use,” is a very broad concept, and for unit tests it typically corresponds to how the author-developer in question looks at their implementation. Better tests are less tied to the implementation, but they nonetheless provide validation of an implementation against someone’s view of the desired behavior. Sometimes, though, it’s also behavior of the developer that we want to validate, using tools like Checkstyle or test coverage tools. I see also a distinct other class of validations, the ones where a unit test validates code constructs against agreements in the team.

I’m specifically talking about code constructs that are locally relevant, so not the ones that the software industry has an opinion about, and probably has (better) tools for also. These are the ones where we crafted our own validations, some of which might qualify as “poor man’s”. However, it’s a pattern that I’ve been happy to use quite regularly, and I figured bringing it to others’ attention. So here’s just a short sampling of such tests in our code base, just to plant the idea with you.

  • Make sure that every JUnit unit test has a @Category annotation.
  • Make sure no one (else) uses this one specific system property.
  • Make sure no one creates JMS payloads that can’t be deserialized. For example, missing a default constructor would be a reason for an object to not be deserializable by Jackson.
  • Make sure that all our API request methods declare payloads as @NotNull.
  • Make sure that all our API endpoints have a version declared.
  • You get the gist…

So, this one, for example:
Make sure that every JUnit unit test has a @Category annotation.

We decided that we need to categorize our JUnit unit tests. This relies on the org.junit.experimental.categories.Category annotation from JUnit, which takes a category defined by us, such as the ubiquitous category UnitTest. This looks something like this:

@Category(UnitTest.class)
public class CollaboCommentTest {
@Test
public void someTest() {
...

After we iterated on this approach for a while, our build systems nowadays really don’t run unit tests anymore if there’s no @Category annotation. So you can see the usefulness of validating that a developer actually adds that annotation, or their work would be for nothing. There’s a unit test called ValidateTestCategoryTest, and I added it in 2014, and it succeeds at catching my own mistakes here, time after time after time.

List offendingClasses = new ArrayList<>();
Collection potentialTestClassFiles = getPotentialTestClassFiles();
for (File file : potentialTestClassFiles) {
String className = getClassName(file);
Class clazz = Class.forName(className);
if (Modifier.isAbstract(clazz.getModifiers())) {
continue; // abstract classes can't be ran anyway
}
if (clazz.isAnnotationPresent(Category.class)) {
continue; // success!
}
if (hasNoTestMethods(clazz)) {
continue; // cop out situation
}
offendingClasses.add(className);
log.error("Missing @Category in " + className);
}
handleResults(potentialTestClassFiles, offendingClasses); // shout at the user, fail the test

Note that getPotentialTestClassFiles is actually just listing all .java files in the project, and it tries to instantiate the Class objects based on their names (before the actual reflection and validation). This test is in a module that we use across many other projects, and we needed to keep its dependencies limited. If you have more power available, Reflections is a useful class/library during such validations.

So, this one, as another example:
Make sure no one (else) uses this one specific system property.

This was mostly focused on preventing the use of the @Value Spring annotation (that injects property values into elements), a requirement that lent itself very well for using Reflections. With reflection we can easily find all method arguments (don’t forget about constructors), setters and fields:

Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(ClasspathHelper.forPackage("com.jamasoftware"))
.setScanners(new FieldAnnotationsScanner(), new MethodAnnotationsScanner(), new MethodParameterScanner()));

handleParameters(reflections.getMethodsWithAnyParamAnnotated(Value.class));
handleParameters(reflections.getConstructorsWithAnyParamAnnotated(Value.class));
handleElements(reflections.getMethodsAnnotatedWith(Value.class));
handleElements(reflections.getFieldsAnnotatedWith(Value.class));

As a next step, we could use reflection to make sure that the element in question wasn’t getting an @Value injected with the system property of interest:

private void handleElements(Set<? extends AnnotatedElement> annotatedElements) {
for (AnnotatedElement annotatedElement : annotatedElements) {
Value value = annotatedElement.getAnnotation(Value.class);
assertValue(annotatedElement, value.value());
}
}

private void assertValue(AnnotatedElement member, String value) {
if (FORBIDDEN_STRING.equals(value)) { // FORBIDDEN_STRING is something like ${propertyName}
fail(format("found %s, file: %s", value, member.toString()));
}
}

It’s often the case that there’re some exceptions to the rules you’re validating for (by design, or just for pragmatics). Those exceptions can just be listed in the test class, and handled in the test accordingly.

In conclusion: next time you make a design decision that will only work well when others follow your lead, make sure it is tested. It is the behavior of developers after yourself, that you’re testing.

This post was originally published on Jama’s blog, on October 19th 2016.