Multi Threaded Unit Tests with Osherove.ThreadTester
The title says "unit tests" but they are really integration tests, since we rely on the cpu and the context switching mechanisms of the OS to do the work for us, but still ,this tries to solve a scenario many people have been asking me about.
The ThreadTester library helps create tests that use multiple threads. It’s main use is to synchronize and “block” the running test until all the threads have finished their job, or a timeout has occurred. It is designed to make the test developer's life easier with a simple API that is readable and quick to use. It's part of the code I'm developing for my upcoming book.
Download:
Source code from svn repository(one of several test libraries there I created):
http://tools.assembla.com/svn/NTestEx
Here is the basic usage of this class:
1) add a reference to Osherove.threadtester into your test project.
2) Write a test that looks like this:
[Test] public void TwoThreads() { Counter c = new Counter(); ThreadTester tt = new ThreadTester(); tt.AddThreadAction( delegate { for (int i = 0; i < 100; i++) { c.Increment(); Thread.Sleep(15); } }); tt.AddThreadAction( delegate { for (int i = 0; i < 100; i++) { c.Increment(); Thread.Sleep(100); } }); tt.StartAllThreads(22500); }
the test above starts two threads and will block until both of them finish their job. The Counter class is a simple class that we'd like to test that only has a count property and an increment method. We want to make sure it is thread safe.
Here's a test that starts up 100 threads against counter:
[Test] public void HundredThreads() { Counter c = new Counter(); ThreadTester tt = new ThreadTester(); for (int i = 0; i < 100; i++) { tt.AddThreadAction(delegate { for (int j = 0; j < 10; j++) { c.Increment(); Thread.Sleep(new Random(j+1).Next(100,300)); } }); } //this test will run for 22.5 seconds tt.RunBehavior=ThreadRunBehavior.RunForSpecificTime; tt.StartAllThreads(22500); }
ThreadTester works by getting a delegate from the developer which will be run in a separate thread. You can add as many separate delegates as you’d like. Each one will be run in a separate thread when you call ThreadTester.StartAllThreads().
AddThreadAction:
Takes a delegate with a void return value and no parameters. In the delegate you write code that would do something that a separate thread would do to your object (like call a method.
RunBehavior:
There are two run behaviors:
RunUntilAllThreadsFinish: For each delegate passed in, a thread will be created that will execute that delegate exactly once. When all threads have finished or if a timeout occurs, the ThreadTester will stop blocking it’s “StartAllThreads()” method.
This behavior is good if you'd like to test your object for a known amount of times.
RunForSpecificTime: For each delegate passed in, a thread will be created that will execute that delegate exactly once. When that thread is finished, a new thread will be created which will execute that delegate again. This behavior will continue until the timeout specified in the StartAllThreads() method has been reached. Then ThreadTester will stop blocking.
This behavior is good if you'd like to stress test your object for a known amount of elapsed time. Even if all threads finish, they will re-run until the time has elapsed.