Diasparsoft Logo Let's write software that people understand.

Home | Contact

Training

JUnit support

Outsourcing

The work that Diasparsoft did for us was outstanding. We are now using the software on a daily basis and their work could well have a dramatic impact on the Reds' organization in the near future.Cincinnati Reds Baseball Club.


Publications

Tips & Tricks

Diasparsoft Toolkit

What is Diaspar?

Interesting Bits RSS

Home » Archives » June 2004 » XMLJUnitResultFormatter behaving badly

[Previous entry: "Online training"] [Next entry: "Maven, CVS and .cvspass"]

06/06/2004: "XMLJUnitResultFormatter behaving badly"


This is for people using Ant/Maven and the junitreport task. If Ant has to report on a test that doesn't pass (failure or error) and that test has a null name, then XMLJUnitResultFormatter will barf in a most unceremonious fashion, with a NullPointerException. Why?

Deep inside, XMLJUnitResultFormatter will attempt to pass a null to a DOM element writer, and that writer will eventually complain loudly about being asked to write a null attribute value. To me, this is quite unreasonable, but that's neither here nor there. To avoid the problem, be sure to set the test name correctly.

You may wonder how I discovered this. Let me use a toy example to illustrate. It has to do with the Parameterized Test Case testing pattern. Suppose we want to execute the same test many times with different rows of test data. JUnit is a little clunky for this, because it specifies fixture data from within, and in this case, we'd like to provide it from without. To do that, we write a test like this one:

package com.diasparsoftware.temp.test;
 
import junit.framework.*;
 
public class ParameterizedTest extends TestCase {
    private int n;
 
    public ParameterizedTest(int n) {
        super("testN");
        this.n = n;
    }
 
    public static Test suite() {
        TestSuite suite = new TestSuite();
        for (int i = 0; i < 100; i++) {
            suite.addTest(new ParameterizedTest(i));
        }
        return suite;
    }
 
    protected void testN() throws Throwable {
        assertTrue(n > 50);
    }
}

We implement suite() to build test objects, and each test takes its fixture data through the constructor. JUnit uses the name of the test to decide which method to invoke, so we invoke super("testN") to reflect the fact that testN() is the "engine" of our test. The bad news is that when half the tests fail, as they will, we see something entirely useless: a bunch of failing tests, all named "testN." How to tell them apart?

Well, if we give them different names, then we can tell them apart. But if we do that, then we can't use the default implementation of runTest(), because that uses the test name to decide which method to invoke for the test. No problem: we override runTest().

package com.diasparsoftware.temp.test;
 
import junit.framework.*;
 
public class ParameterizedTest extends TestCase {
    private int n;
 
    public ParameterizedTest(int n) {
        this.n = n;
    }
 
    public static Test suite() {
        TestSuite suite = new TestSuite();
        for (int i = 0; i < 100; i++) {
            suite.addTest(new ParameterizedTest(i));
        }
        return suite;
    }
 
    protected void runTest() throws Throwable {
        setName("Test #" + n);
        assertTrue(n > 50);
    }
}

Do you see my defect here? When we run the tests, now the failing ones ought to identify themselves correctly as "Test #0", "Test #1", ...

Oops.

I set the name of the test too late. I used to be doing it in the constructor, and now I'm doing it in runTest(). When the test runner generates testing events, it'll have a null test name, which leads to our reporting problem. Let's move the setName() up to the constructor and we're good.

package com.diasparsoftware.temp.test;
 
import junit.framework.*;
 
public class ParameterizedTest extends TestCase {
    private int n;
 
    public ParameterizedTest(int n) {
        setName("Test #" + n);
        this.n = n;
    }
 
    public static Test suite() {
        TestSuite suite = new TestSuite();
        for (int i = 0; i < 100; i++) {
            suite.addTest(new ParameterizedTest(i));
        }
        return suite;
    }
 
    protected void runTest() throws Throwable {
        assertTrue(n > 50);
    }
}

Ah! Much better. So why didn't I find this problem before? It's simple, really, and a little embarrassing.

I forgot to watch the tests fail before I made them pass, so I never saw the problem until I made a change that made them all fail. Yet another reason to watch 'em fail before making 'em pass.

Replies: 1 Comment

on Monday, June 14th, q.y said

Have you tried how to do parameters completely externized?