Problem when Unit-testing multi threaded code
I have implemented a Pipe
class that internally uses a BlockingQueue
to store the data it receives.
There 开发者_如何学Pythonare two situations when the BlockingQueue
will block the calling thread:
- The calling thread makes a call to
Dequeue()
and the queue is empty. It will block the thread until there is an item to retrieve. - The calling thread makes a call to
Enqueue()
and the queue is full. It will block the thread until there is space again to insert the data.
My initial idea was that instead of having the Pipe
class instantiate the BlockingQueue
, I'd pass an instance of an IQueue
to it by constructor injection. That way, when testing, I'd pass it an instance of a NonBlockingQueue
, so I wouldn't have to bother with threading problems (when I'm doing Unit-tests, both for the Pipe
class and for other classes that use Pipes
I'd like to just add things to the queues and not having to think if they are full already and things like that).
The trouble is that when doing this, I'm actually making my Pipe
behave in 2 totally different ways, depending on the kind of IQueue
instance I'm passing to it:
In a
BlockingQueue
, if the queue is empty and you try to retrieve something from it, it'll block until it gets something. In aNonBlockingQueue
it'll just throw up an exception.In a
BlockingQueue
, if the queue is full and you try to add something, it will wait until someone dequeues an element and there is space again. ANonBlockingQueue
version will either throw up anFullQueueException
or will allow an "infinite" number of elements.
That is, there is no "single contract". I think this approach is definitely wrong.
What is a more apropriate approach to this?
Edit to Mitch:
This is being used to implement a Pipe&Filter system: each Filter has an input and output pipes. Each filter then is implemented with code of the form
char c;
while ((c = inputPipe.ReadChar()) != STREAM_TERMINATOR) {
//I don't have to care
//if right now there is any data. I know that if there isn't,
//the thread will block and this will continue after there is some.
...do processing
outputPipe.WriteChar(something);
}
outputPipe.WriteChar(STREAM_TERMINATOR);
so I guess yes, having blocking pipes / queues is the behaviour I want.
The idea of unit testing is that you test small portions of code, and with a lot of those you eventually test all your code. There is a reason it is broken down in pieces, testing one piece at a time is easier than testing everything simultaneously. In the code you use the BlockingQueue, and in the testing you use a NonBlockingQueue, which introduces all kinds of challenging aspects and this defeats the purpose of unit testing... In the tests you should simplify, not complicate. So why not just use a BlockingQueue there too ? You state that the threading might be a problem, but at least use a simple implementation of IQueue which from the outside works exactly like a BlockingQueue, but without the threading problems. In the unit tests you should be able to provide an instance of such in IQueue which doesn't throw exceptions. For example, instead of when you'd normally throw an exception since the queue is empty, just come up with a new item. That is allowed in tests....
Your idea of having a constructor injected implementation of IQueue
is correct.
Now what do you want to unit test here?
IQueue
implementationPipe
responsibility (apart from interacting withIQueue
implementation)- Interaction of
Pipe
andIQueue
implementation
You want to focus on point #3. I think you need to think of responsibilities here. Whose responsibility is it to ensure that a queue is blocked or not. It is the responsibility of a IQueue
implementation and not Pipe
. So, IQueue
contract will just have an 'action' (a method call) that happens when we have an exceptional situation (dequeuing from an empty queue or adding to a full queue). You want to unit test this interaction, which means just testing whether the action method gets called in the exceptional situation.
精彩评论