Ensuring JUnit test suite contains all tests

While cleaning up today one of my projects I found a JUnit test that was not part of a test suite and never was executed. ‘Never again’ I say and here is how: a test suite that scans base packages for tests.

The particular code:

  • Is tailored to the specific project’s needs 
  • Compares the tests which are declared in the test suite with the ones actually present and fails with a detailed report should they not match
  • Uses the Reflections [1] library to scan sub packages for executable tests which extend a given base class

Areas for improvement:

  • Tests must currently extend a base class (here: BaseTest) as I have not found a way for Reflections to discover tests in the sub packages when they are not annotated or do not extend a base class
  • When test suites are discovered their tests should also somehow be handled

@RunWith(Suite.class)
@Suite.SuiteClasses({ Test1.class, Test2.class, Test3.class })
public class TestSuite {

static String basePackage = TestSuite.class.getPackage().getName();

private static Set<Class> getDeclaredTests() {
Annotation[] annotations = TestSuite.class.getAnnotations();
for (Annotation annotation : annotations) {
if (!annotation.annotationType().equals(SuiteClasses.class))
continue;
Suite.SuiteClasses testClasses = (Suite.SuiteClasses) annotation;
Set<Class> cset = new HashSet<Class>();
for (Class c : testClasses.value())
cset.add(c);
return cset;
}
return null;
}

private static boolean isAbstract(Class c){
return Modifier.isAbstract(c.getModifiers());
}

private static Set<Class> getActualTests() {

Reflections reflections = new Reflections(basePackage, MethodAnnotationsScanner.class);
Set<Class> tests = (Set)reflections.getSubTypesOf(BaseTest.class);
for (Iterator<Class> ite = tests.iterator();ite.hasNext();){
Class c = ite.next();
if (isAbstract(c))
ite.remove();
}
return tests;
}

private static List<Class> getMissingTests(Set<Class> realTests, Set<Class> declaredTests) {
List<Class> missingTests = new ArrayList<Class>();
for (Class test : realTests)
if (!isAbstract(test) && !declaredTests.contains(test))
missingTests.add(test);
return missingTests;
}

@BeforeClass
public static void checkThatAllTestsAreIncluded() {
Set<Class> declaredTests = getDeclaredTests();
Set<Class> realTests = getActualTests();
List<Class> missingTests = getMissingTests(realTests, declaredTests);
String failMessage = "";
if (!missingTests.isEmpty()) {
String report = "";
for (Class test : missingTests) {
report += "\n" + test.getCanonicalName();
}
failMessage += "Found tests that are not included in the suite: "
+ report;
}
if (realTests.size() < declaredTests.size()){
failMessage += "\nFound real tests : " + realTests.size()
+ " while " + declaredTests.size() + " were declared. Missing tests:";
for (Class c:declaredTests)
if (!realTests.contains(c))
failMessage+="\n"+c.getCanonicalName();
}
if (!StringUtils.isEmpty(failMessage))
fail(failMessage);
}
}

Resources

[1] Reflections library
http://code.google.com/p/reflections/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s