« Build Speed | Main | Tests and Refactoring »

More Broken Tests

Listen to this articleListen to this article

Following on from my entry yesterday on broken tests I came across another interesting problem I see from time to time in peoples test code.

Here's a very simple example to demonstrate. As far as I'm concerned it's a big fat broken test:

public class FileBuilderTest extends TestCase {
    public void testHeaderIsWritten() throws Exception {
        StringWriter writer = new StringWriter();
        FileBuilder builder = new FileBuilder(writer);

        BufferedReader reader = new BufferedReader(new StringReader(writer.toString()));

        String line = reader.readLine();
        assertNotNull("too few lines", line);
        assertEquals(FileBuilder.HEADER, line);

        ...
    }
}
 
public class FileBuilder {
    public static final String HEADER = "# Created by " + FileBuilder.class.getName();
    
    private final Writer _writer;

    public FileBuilder(Writer writer) throws IOException {
        if (writer == null) {
            throw new IllegalArgumentException("writer can't be null");
        }
        _writer = writer;
        
        writer.write(HEADER);
    }
}
 

Seems pretty sensible right? We check to make sure that the header gets written to the stream. Putting aside the are tests for testing or design debate for a moment, one thing remains clear - a test should break if I change the class in anyway that breaks the intended behaviour.

Right?

If so, then this test is truly crippled. Don't believe me? Well just change the value of header to some absolute garbage and re-run the test.

Did it break?

No. Why? Because the test never checks that what gets written is what is supposed to be written. Instead, it is simply checking that whatever header the FileBuilder has defined is what gets written. So anyone can inadvertently (or deliberately as we have done) change that header and the test never breaks.

I'd rather see this:

public class FileBuilderTest extends TestCase {
    public void testHeaderIsWritten() throws Exception {
        StringWriter writer = new StringWriter();
        FileBuilder builder = new FileBuilder(writer);

        BufferedReader reader = new BufferedReader(new StringReader(writer.toString()));

        String line = reader.readLine();
        assertNotNull("too few lines", line);
        assertEquals("# Created by " + FileBuilder.class.getName(), line);

        ...
    }
}
 
public class FileBuilder {
    private static final String HEADER = "# Created by " + FileBuilder.class.getName();
    
    private final Writer _writer;

    public FileBuilder(Writer writer) throws IOException {
        if (writer == null) {
            throw new IllegalArgumentException("writer can't be null");
        }
        _writer = writer;
        
        writer.write(HEADER);
    }
}
 

I recall one of the things I found disturbing when reading (one of) Mr. Becks TDD book being his suggestion that our first example is in fact good practice because we are removing duplication. Personally, I don't care about duplication in tests nearly (if at all) as much as I do in production code.

An interesting variation I heard about involved a developer essentially coding the same algorithm in both the production class and the test so they could verify the results.

At any rate, hopefully you can see the similarity in both cases - the tests weren't explicit about what the expected results should be instead using a "calculated" value. I'd much rather see the test explicitly state exactly what it expected to be written. That way if someone accidentally does say a find+replace within our FileBuilder code, we will catch it.

Comments

Seems to me that the two tests you describe have different goals, both valid. The first is confirming that we are using FileBuilder.HEADER, and the second is confirming that the FileBuilder.HEADER string is correct. If either of those is not happening, then it would indicate a problem. The first is a code quality issue, the second is a functionality issue. Check them both!

I have to agree with Dave here. What you have to do is figure out what it is you're really testing. Either you can have your second version of the test if you're simply looking to verify the first line of a file is meant to be a specific format. If you want to be sure it defines this in a constant as well, I'd keep the first version of the test and add a second test asserting that HEADER was equal to the expected format.

Got to agree with both Chris and Dave. In general, I would expect to see a test that verified that the header is made into the correct format, and then a second test to see that the file gets written correctly.

Depending on the complexity of the pieces, the file writing test may not even use the whole thing; plugging in a mock would go well.

OTH, if there _isn't_ a second test, then there is a big problem. I see this sort of thing in Struts-based tests all the time: the code looks to see if a form is present in the right place, but doesn't look to see if it's been populated at all.

Hmmmm....Though it's an interesting point, I'm not convinced. I'm not sure I want my automated tests testing code quality. Surely (in this case) thats better checked by something like checkstyle using a generic rules approach rather than an individual test for each specific instance?

Sorry Dave & Chris, but your reasoning seems a bit dumb to me. FileBuilder.HEADER was only public in the first version so the test could access it. In the second version, it is now part of the private implementation of the class. Testing to see that a FileBuilder object uses a private FileBuilder constant is ridiculous.

Hey Marty. Exactly my thoughts too. I don't want to care if it comes from an instance field, a static final field, an array or even a text/properties file for that matter. I just want to know that it generates the appropriate text. Anything else is a matter of "style" which, as I've said, I'd rather something else was responsible for hcecking.

I'd rather separate them.

i.e.

assertEquals("# Created by " + FileBuilder.class.getName(), FileBuilder.HEADER);
assertEquals(FileBuilder.HEADER, line);

cause then when it fails you know which one is broken.

I agree that there is no need to test that it uses the header (and you can't anyway - you can only test that it prints the same value...) but from a diagnosis point of view, I would find it quick to know what broke if the test was written as above.

Post a comment