johnrl
johnrl

Reputation: 583

Test composite object when components already tested -> redundant?

I've got a general question illustrated with a concrete example. How much would you recommend testing composite objects when all the component objects are already tested?

As a concrete example consider the NullTerminatedStringReader below. It reads, from a bytebuffer, a null terminated string. To do this it uses Javas Charset decoder.

I of course want to test my NullTerminatedStringReader. It should be able to read all kinds of strings like UTF8, UTF16, ASCII and so on.

Imagine I had written the CharsetDecoder and tested that it would decode characters from all kinds of possible charsets. It's REALLY well tested and tried and I have no doubt that it works. Now I write a NullTerminatedStringReader that uses the CharsetDecoder. In theory I want the NullTerminatedStringReader to be able to handle all strings of charsets that the CharsetDecoder can decode. If I was using TDD I would want to drive my design, so I would write a lot of tests testing each charset decoding for NullTerminatedStringReader:

...
void testNullTerminatedStringReaderCanDecodeUTF8String()
void testNullTerminatedStringReaderCanDecodeASCIIString()
...

But this seems redundant because in the test for CharsetDecoder I have all these tests:

...
void testCharsetDecoderCanDecodeUTF8Char()
void testCharsetDecoderCanDecodeASCIIChar()
...

I'm not sure what to do here because without the tests for NullTerminatedStringReader, how can I drive it's design to support all of these decodings? Am I testing on the wrong level for NullTerminatedStringReader? If I don't make the tests it also seems something is missing because in theory I know the NullTerminatedStringReader is using CharsetDecoder and I know that all it does is append chars to form a string, so there's not much that can go wrong here. If CharsetDecoder works so should NullTerminatedStringReader. But what if it didn't use CharsetDecoder - I think it is more clear to assume no knowledge of NullTerminatedStringReader's implementation and then write the tests, however this results in what seems like redundant tests.. dillema? You bet.

class NullTerminatedStringReader
{
    private Charset charset;

    public String read(ByteBuffer buffer)
    {
        StringBuilder sb = new StringBuilder();
        while (true)
        {
            char charVal = readChar(buffer, charset.newDecoder()); // unicode char, possibly span several bytes
            if (charVal == '\0')
                break;
            sb.append(charVal);
        }

        return sb.toString();
    }

    private char readChar(ByteBuffer buffer, CharsetDecoder decoder) {...}
}

Upvotes: 4

Views: 494

Answers (2)

DaveFar
DaveFar

Reputation: 7457

I think you can consider this situation as some kind of integration test: To cope with complexity, you are modularizing. For this, you

  • need to know the contract of the component you re-use (the pre- and post-condition of CharsetDecoder you use in readChar), so that your NullTerminatedStringReader can use some other component that fulfillS the contract. But you do not need to know that you are using the CharsetDecoder implementation.
  • need not retest the functionality of the component you re-use: You can simply assume the postconditions as long as you meet the preconditions.
  • use those preconditions for TDD to drive your design of NullTerminatedStringReader, but you can assume that the postconditions hold. And use the postconditions for TDD to drive your design of some implementation, e.g. CharsetDecoder, if you still need to create it. But for this, you can assume that the preconditions will be met.

Upvotes: 1

mikera
mikera

Reputation: 106361

When testing a composite object I generally just test for two things:

  • Does it support the composite end-to-end use cases that it is designed for?
  • Does any incremental capability added by the composite object work?

In general, I wouldn't try to test the sub-objects comprehensively as they should have their own test cases to do that. i.e. you should assume that your own sub-objects work, in the same way that you assume that java.util.ArrayList works.

So in your example of the NullTerminatedStringReader, I'd probably only test that it works with one or two alternative charsets (i.e. prove that it is calling the correct CharsetDecoder) and trust that the CharsetDecoder was well enough tested to work will all other charsets.

Upvotes: 1

Related Questions