Unit Testing Locking
April 21, 2014
I’ve often found it difficult to unit test code which must lock a mutex. Suppose that I need to write an internally-locked queue. How can I write a failing test for code like this:
Locking in the correct place is rather important, since doing so incorrectly can lead to deadlocks on one hand or race conditions on the other. Something so important should be easy to test.
Using a wrapper
It is possible to make this code testable by wrapping the threading API in a helper class. Then the helper class can be used in the unit tests to validate the locking behavior.
So we can write a test that looks like this:
Although this works, I don’t think it is desirable for two reasons:
- It exposes something about the locking scheme on the API of the
queue
class, which seems unrelated to the behavior of the class. - It adds the overhead of a virtual function call each time a mutex is locked. This overhead can be significant if the mutex is locked often.
Cleaning up the API
The C++11 threading library provides some useful tools to clean up this API, eliminate unnecessary overhead, and allow the code to be testable. Scope-based locking in C++11 can be implemented with the std::lock_guard<T>
class, where T is the type of the mutex to lock. In most cases, production code can use the type std::mutex
for T. But if we use something like policy-based design, we can expose the mutex type as an optional template parameter.
Now the API for our queue is an minimal as possible, but we can still inject a mock mutex type in order to verify that the enqeue
method correctly takes the lock.
Once we have this unit testing scheme in place, it is possible to verify that the lock is correctly released. We can also test other methods of the queue
class, like try_dequeue
and empty
without too much difficulty. Thanks to the template-based implementation of std::lock_guard
in C++11, unit testing code that requires a lock is now possible, and dare I say, surprisingly easy.
You can find an example of the final code the compiles with Visual Studio 2013 here.