views:

125

answers:

4

I'm designing a library for myself that allows the chaining of streams of data. Let me paint the scenario:

I create a SerialDatastream which is the bottom layer and reads from and writes to a COM port.

I pass a pointer to this to the constructor of a ProtocolDatastream, which interprets the bytes when read from the serial datastream (although it only has knowledge that it fulfills my Datastream interface), and returns them as protocol data units.

Now let's say I want to take the information from the serial port and also log it byte for byte. I insert a TeeDatastream in the middle which reads from one source, but outputs to two destinations:

               +-----> Log
               |
Serial ----> Tee ----> Protocol

TeeDatastream is implemented in the following way: when a read operation is performed from one branch, it buffers the data into a member variable. Then, when a read operation is performed on the other branch, it reads the already-buffered data.

(this works fine, by the way)

What this means is that, after each operation, the class must check to see if there exists data that has been read from both branches. This data can then be discarded and so the buffer shrinks as well as growing. However, this is completely invisible to any client of the class. So my question is: what pattern should be used to test invisible but necessary parts of an implementation?

+2  A: 

What you need are unit tests for Tee itself; independent of its use later in other unit tests as part of the plumbing.

For these new unit tests, the "invisible part" is actually what they must cover. This is no longer a hidden feature but part of the API of Tee.

Later, when you're sure that Tee works correctly (and have the necessary tests to make sure it stays that way), you can use it and be oblivious to the fact how it works.

Aaron Digulla
I do have unit tests for Tee itself, to check that it reads and writes correctly, and so on. The problem is that the internal buffer is completely hidden from the client, so can't be tested. Although... are you saying I should lift the buffer from the code into, say, a TeeBuffer class, with its own unit tests, and then refactor Tee to use TeeBuffer? That sounds like a reasonable strategy.
Kaz Dragon
Yes, since the buffer is the most important feature, it must be tested somehow.
Aaron Digulla
Ok, this one seemed the most sound to me: by lifting the feature out of the class, it becomes possible to test that class in isolation. Thus, when the Tee uses the now-tested feature, it will implement this functionality without the requirement to expose its internals. Thanks.
Kaz Dragon
True story: when implementing this extra test, I found a bug. Not a huge one, but definitely there. That seems to happen every time I skip testing for "just this one quick thing." Things like this convince me that TDD is the best software implementation strategy.
Kaz Dragon
@Kaz: +1 Happens to all of us all the time. :)
Aaron Digulla
A: 

Unless I am missing something, it seems that you are looking for plain unit testing. This type of testing allows you to test both the public and the private parts of your code.

Konamiman
A: 

If you're using Java, see this question. I'm not sure you're in exactly the same situation, but you may be able to use the techniques outlined there.

MattK
+1  A: 

In the unit test cpp file locally declare a friend class and declare each protected member function you want to test.

class BlahTestable : public Blah
{
public: 
    using Blah::protectedfunction1;
    using Blah::protectedfunction2;
    etc....
};

Then in the unit test do

// for public members
TEST_F(BlahTest, publicfunction) {
    Blah s;
    s.publicfunction();
}

// for protected members
TEST_F(BlahTest, protectedfunction1) {
    BlahTestable s;
    s.protectedfunction1();
}
Martin Beckett
this example is for googletest but the same principle applies for any other.
Martin Beckett
I don't particularly like this method, since it exposes the internals of the class to the client. The client doesn't need access to knowledge of how the buffer is managed; it just needs to happen.
Kaz Dragon
A contrived example - but if you have a class with many complex internal methods and only a single public one. Then it's useful to be able to test each function rather than just the output as a whole.
Martin Beckett