Coming Up for Air

Running a Single JUnit Test

Part of my job as a developer is writing unit tests. Lately, though, I’ve been spending more and more of my time in our tests, which take a long, long time to run. For example, to run the GlassFish Admin Console’s StandaloneTest class, the last run took 17 minutes and 36 seconds. Clearly, something needs to be done to speed that up overall, but I have to wait for the entire class to run just so that I can see if my one new/changed test works. Try as I might, I have not been able to find a way to make the surefire Maven plugin run just that one test method. This morning, though, I happened to stumble across a new feature of JUnit (as of 4.7, if I read correctly) that did the trick for me, which I’ll share here.

This new feature is a MethodRule. It’s an interface that a user can implement, and, when coupled with the @Rule attribute, allows this rule to applied in a very AOP-like manner to each test. Since code is often better than words, here’s the MethodRule I implemented for the console tests:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class SpecificTestRule implements MethodRule {
    protected String method;
    public SpecificTestRule() {
        method = System.getProperty("method");
    }
    @Override
    public Statement apply(final Statement statement,
            final FrameworkMethod frameworkMethod, final Object o) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                boolean runMethod = false;
                Ignore ignore = frameworkMethod.getAnnotation(Ignore.class);
                if ((method != null)  && method.equals(frameworkMethod.getName())) {
                    runMethod = true;
                } else if (ignore == null) {
                    runMethod = true;
                }
                if (runMethod) {
                    statement.evaluate();
                }
            }
        };
    }
}

There’s really not much going on here. In the constructor, I look up the value of the method system property and store it on the instance. In the apply() method, we check to see if method is null. If it is (i.e., the user did not specify this property on the command line, so all test methods are to be run), we execute the test method in question. If it is not, then the user has requested that only a specific method be run, so check to see what the current method is. We do that by calling frameworkMethod.getName(). If that equals method, then we evaluate the Statement. Otherwise, we exit without doing anything.

With that rule defined, let’s take a look at how that is applied to the tests. All of the console tests extend a base class, so I added this snippet to that base class:

1
2
@Rule
    public SpecificTestRule specificTestRule = new SpecificTestRule();

In this case, that particular instance variable is never used, so this usage seems strange, but it is possible for the MethodRule to provide data to test (such as the test name). In our case, though, we just want to apply the rule, so we add the instance variable and forget about it.

With those two pieces of code in place, we can now run individual test methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyTest extends MyBase { // The rule is defined in the base
    @Test
    public void foo() {
        System.out.println("foo");

    }

    @Test
    public void bar() {
        System.out.println("bar");
    }

    public void baz() {
        System.out.println("baz");
    }
}

For that test, we can execute mvn -Dtest=MyTest -Dmethod=foo test, and expect output like this:

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running MyTest
foo
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.048 sec
Results :
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

A possible enhancement would be to allow for a comma-delimited list of methods to run (mvn -Dtest=MyTest -Dmethod=foo,baz test), but this is a nice start, I think.

Update: After posting this, I found a bug where the @Ignore annotation was ignored. I have updated the MethodRule implementation above to fix the bug. Yes, the code can be more concise, but I’m blogging it as I develop and test it, and, that aside, this is more readable than super-compact code, and that’s not nothing. :P

tags: JUnit testing

Quotes

Sample quote

Quote source