Applying DRY to JUnit Categories

Long awaited, JUnit 4.8 introduced support for categorizing test cases.

A category marker is simply a class or interface, e.g.

public interface SlowTests {}

Tests can be marked using the @Category annotation:

public class A {
    @Test
    public void a() {}

    @Category(SlowTests.class)
    @Test
    public void b() {}
}

The annotation works both on methods and classes:

@Category(SlowTests.class)
public class B {
    @Test
    public void c() {}
}

Test suites that include or exclude the SlowTests category are defined by specifying the Categories runner and using the @ExcludeCategory or @IncludeCategory annotation, respectively:

@RunWith(Categories.class)
@SuiteClasses( { A.class, B.class })
@ExcludeCategory(SlowTests.class)
public class AllFastTests extends AllTests {}

@RunWith(Categories.class)
@SuiteClasses( { A.class, B.class })
@IncludeCategory(SlowTests.class)
public class AllSlowTests extends AllTests {}

In this example, AllFastTests would execute only A.a while AllSlowTests would ignore A.a but run A.b and B.c.

However, there is a major issue in the above suite declarations: they violate the DRY principle. Both test suites list all test classes in the @SuiteClasses annotation. While it seems feasible to maintain the list of test classes at two locations for a small number of classes, it certainly is not a viable option in a real-world setting, especially when there are multiple categories.

Fortunately, there is a simple solution: use inheritance. You can define the list of test classes once in a normal test suite …

@RunWith(Suite.class)
@SuiteClasses( { A.class, B.class })
public class AllTests {}

… and declare subclasses that filter the list of classes by category:

@RunWith(Categories.class)
@ExcludeCategory(SlowTests.class)
public class AllFastTests extends AllTests {}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
public class AllSlowTests extends AllTests {}
blog comments powered by Disqus