Problem with Unit-testing multi-threaded code

I implemented a Pipe class that internally uses BlockingQueue to store the received data.

There are two situations where BlockingQueue blocks the calling thread:

  • The calling thread calls Dequeue() and the queue is empty. It will block the thread until an element is found.
  • The calling thread calls Enqueue() and the queue is full. It will block the stream until there is again room for inserting data.

My initial idea was that instead of the Pipe class creating an instance of BlockingQueue , I passed it an IQueue instance by installing the constructor. That way, when testing, I would give him an instance of NonBlockingQueue , so I would not have to worry about problems with threads (when I run Unit tests for both the Pipe class and other classes that use Pipes I would just like to add things in line and do not think if they are already full and the like).

The problem is that in doing so, I actually make my Pipe behave differently, depending on the type of IQueue instance that I pass to it:

  • In BlockingQueue , if the queue is empty and you are trying to recover something from it, it will be blocked until it receives something. In NonBlockingQueue it will just throw an exception.

  • In BlockingQueue , if the queue is full and you are trying to add something, it will wait until someone removes the item and a space appears again. The NonBlockingQueue version NonBlockingQueue either FullQueueException or allow an "infinite" number of elements.

That is, there is no single agreement. I think this approach is definitely wrong.

What is a more suitable approach to this?

Change to Mitch:

This is used to implement the Pipe & Filter system: each filter has input and output channels. Then each filter is implemented with a form code.

 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 think, yes, blocking pipes / queues is the behavior I want.

+4
source share
2 answers

The idea of ​​unit testing is that you test small pieces of code and with a lot of those who end up testing all of your code. There is a reason why it breaks into pieces, testing one part at a time is easier than testing everything at the same time. In the code that you use BlockingQueue, and when testing you use NonBlockingQueue, which introduces all kinds of complex aspects, and this is detrimental to the objectives of unit testing ... In tests, you should simplify, not complicate. So why not just use a BlockingQueue? You argue that streaming can be a problem, but at least use a simple IQueue implementation that works just like BlockingQueue from the outside, but without threading issues. In unit tests, you should be able to provide an instance of one in IQueue that does not throw exceptions. For example, instead of when you usually throw an exception, since the queue is empty, just come up with a new element. This is allowed in tests ....

+1
source

Your idea of ​​having a built-in implementation of the IQueue constructor IQueue correct.

Now what do you want unit test here?

  • IQueue implementation
  • Pipe responsibility (other than interacting with the IQueue implementation)
  • Interaction between the implementation of Pipe and IQueue

You want to focus on point 3. I think you need to think about responsibility here. Whose responsibility is to block the queue or not. This is the responsibility of implementing IQueue , not Pipe . Thus, the IQueue contract will only have an β€œaction” (method call) that occurs when we have an exception (delete from an empty queue or add to a full queue). You want unit test this interaction, and that means just checking if the action method is called in an exception.

+1
source

Source: https://habr.com/ru/post/1334022/


All Articles