Getting started with unit testing is not hard, the only problem is that most programs are more than a simple calculator with two parameters and a returns value that is easy to verify. Writing unit tests for multi-threaded code is harder still. Over the years I discovered useful patterns that helped me to test multi-threaded and asynchronous code and enabled the creation of deterministic, simple and robust unit tests. Come learn how to test code that uses concurrency and parallelism – so that the excuses of not writing unit tests for such code would become as obsolete as a single core processors.
8. public void Start() {
_worker = new Thread(() => {
while (_isAlive) {
Thread.Sleep(1000);
var msg = _messageProvider.GetNextMessage();
//Do stuff
LastMessage = msg;
}
});
_worker.Start();
}
9. [TestMethod]
public void ArrivingMessagePublishedTest()
{
var fakeMessageProvider = A.Fake<IMessageProvider>();
A.CallTo(() => fakeMessageProvider.GetNextMessage()).Returns("Hello!");
var server = new Server(fakeMessageProvider);
server.Start();
Thread.Sleep(2000);
Assert.AreEqual("Hello!", server.LastMessage);
}
10. Sleep
× Time based - fail/pass inconsistently
× Test runs for too long
× Hard to investigate failures
12. [TestMethod]
public async Task ArrivingMessagePublishedTest()
{
var fakeMessageProvider = A.Fake<IMessageProvider>();
A.CallTo(() => fakeMessageProvider.GetNextMessage()).Returns("Hello!");
var server = new Server(fakeMessageProvider);
server.Start();
await Task.Delay(2000);
Assert.AreEqual("Hello!", server.LastMessage);
}
14. Code under Test
Start
Humble object
Async
Perform action
Perform Action
Assert Result
Production
Code http://xunitpatterns.com/Humble%20Object.html
15. public void Start() {
_worker = new Thread(() => {
while (_isAlive) {
Thread.Sleep(1000);
var msg = _messageProvider.GetNextMessage();
//Do stuff
LastMessage = msg;
}
});
_worker.Start();
}
16. public void Start() {
_worker = new Thread(() => {
while (_isAlive) {
Thread.Sleep(1000);
_messageHandler.HandleNextMessage();
}
});
_worker.Start();
}
17. [TestMethod]
public void ArrivingMessagePublishedTest()
{
var fakeMessageProvider = A.Fake<IMessageProvider>();
A.CallTo(() => fakeMessageProvider.GetNextMessage()).Returns("Hello!");
var messageHandler = new MessageHandler(fakeMessageProvider);
messageHandler.HandleNextMessage();
Assert.AreEqual("Hello!", messageHandler.LastMessage);
}
18. public class MessageManager
{
private IMesseageQueue _messeageQueue;
public void CreateMessage(string msg)
{
// Here Be Code!
_messeageQueue.Enqueue(message);
}
}
public class MessageClient
{
private IMesseageQueue _messeageQueue;
public string LastMessage { get; set; }
private void OnMsg(object o, EventArgs e)
{
// Here Be Code!
LastMessage = e.Message;
}
}
40. [TestMethod]
public void TestUsingSignal() {
var waitHandle = new ManualResetEventSlim(false);
var fakeOtherClass = A.Fake<IOtherClass>();
A.CallTo(() => fakeOtherClass.DoSomething(A<int>._)).Invokes(waitHandle.Set);
var cut = new ClassWithAsyncOperation(fakeOtherClass);
cut.DiffcultCalcAsync(2, 3);
var wasCalled = waitHandle.Wait(10000);
Assert.IsTrue(wasCalled, "OtherClass.DoSomething was never called");
Assert.AreEqual(5, cut.Result);
}
42. × Harder to investigate failures
× Cannot test that a call was not made
Test runs for too long but only when it fails
Use if other patterns are not applicable
43. Concurrent unit testing patterns
• Humble object
• Test before – test after
Avoid Concurrency
• Fake & Sync
• Async in production - sync in test
Run in single thread
• The Signaled pattern
• Busy assertion
Synchronize test