SlideShare a Scribd company logo
Unit testing patterns for concurrent code 
Dror Helper 
drorh@oz-code.com | @dhelper | http://blog.drorhelper.com 
Examples: https://github.com/dhelper/ConcurrentUnitTesting
About.ME 
• Senior consultant @CodeValue 
• Developing software (professionally) since 2002 
• Mocking code since 2008 
• Test Driven Developer 
• Blogger: http://blog.drorhelper.com
We live in a concurrent world! 
The free lunch is over! 
• Multi-core CPUs are the new standard 
• New(er) language constructs 
• New(ish) languages
Meanwhile in the unit testing “world” 
[Test] 
public void AddTest() 
{ 
var cut = new Calculator(); 
var result = cut.Add(2, 3); 
Assert.AreEqual(5, result); 
}
The dark art of concurrent code 
Several actions at the same time 
Hard to follow code path 
Non deterministic execution
Good unit tests must be: 
• Trustworthy 
– Consistent results 
– Only fail due to bug or requirement change 
• Maintainable 
– Robust 
– Easy to refactor 
– Simple to update 
• Readable
Concurrency test “smells” 
× Inconsistent results 
× Untraceable fail 
× Long running tests 
× Test freeze
How can we test this method 
public void Start() 
{ 
_worker = new Thread(() => { 
while (_isAlive) { 
Thread.Sleep(1000); 
var msg = _messageProvider.GetNextMessage(); 
//Do stuff 
LastMessage = msg; 
} 
}); 
_worker.Start(); 
}
Testing start take #1 
[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); 
}
Test smell - “Sleep” in test 
× Time based - fail/pass inconsistently 
× Test runs for too long 
× Hard to investigate failures
“In concurrent programming if 
something can happen, then sooner 
or later it will, probably at the most 
inconvenient moment” 
Paul Butcher – Seven concurrency models in seven weeks
Testing start take #2 
[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); 
}
Solution: avoid concurrent code!
Pattern #1 – humble object pattern 
We extract all the logic from the hard-to-test component into a component 
that is testable via synchronous tests. 
http://xunitpatterns.com/Humble%20Object.html 
Perform Action 
Code under Test 
Start 
Humble object 
Async 
Perform action 
Assert Result 
Production 
Code
public void Start() 
{ 
_worker = new Thread(() => { 
while (_isAlive) { 
Thread.Sleep(1000); 
var msg = _messageProvider.GetNextMessage(); 
//Do stuff 
LastMessage = msg; 
} 
}); 
_worker.Start(); 
}
public void Start() 
{ 
_worker = new Thread(() => { 
while (_isAlive) { 
Thread.Sleep(1000); 
_messageHandler.HandleNextMessage(); 
} 
}); 
_worker.Start(); 
}
And finally – the test 
[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); 
}
Concurrency as part of program flow 
public class MessageManager 
{ 
private IMesseageQueue _messeageQueue; 
public void CreateMessage(string message) 
{ 
// Here Be Code! 
_messeageQueue.Enqueue(message); 
} 
} 
public class MessageClient 
{ 
private IMesseageQueue _messeageQueue; 
public string LastMessage { get; set; } 
private void OnMessage(object sender, EventArgs e) 
{ 
// Here Be Code! 
LastMessage = e.Message; 
} 
}
Test before – Test After 
Start 
Code under test 
Async 
Logic2 
Assert results 
Logic1 
Start Logic1 Fake Assert Results 
Start Fake Logic2 Assert Results
Testing Flow part 1 
[TestMethod] 
public void AddNewMessageProcessedMessageInQueue() 
{ 
var messeageQueue = new AsyncMesseageQueue(); 
var manager = new MessageManager(messeageQueue); 
manager.CreateNewMessage("a new message"); 
Assert.AreEqual(1, messeageQueue.Count); 
}
Testing Flow part 2 
[TestMethod] 
public void QueueRaisedNewMessageEventClientProcessEvent() 
{ 
var messeageQueue = new AsyncMesseageQueue(); 
var client = new MessageClient(messeageQueue); 
client.OnMessage(null, new MessageEventArgs("A new message")); 
Assert.AreEqual("A new message", client.LastMessage); 
}
Avoid concurrency Patterns 
The best possible solution 
No concurrency == no problems 
× Do not test some of the code 
× Not applicable in every scenario
How can we test this class? 
public class ClassWithTimer 
{ 
private Timer _timer; 
public ClassWithTimer(Timer timer) 
{ 
_timer = timer; 
_timer.Elapsed += OnTimerElapsed; 
_timer.Start(); 
} 
private void OnTimerElapsed(object sender, ElapsedEventArgs e) 
{ 
SomethingImportantHappened = true; 
} 
public bool SomethingImportantHappened { get; private set; } 
}
Not a good idea 
[TestMethod] 
public void ThisIsABadTest() 
{ 
var timer = new Timer(1); 
var cut = new ClassWithTimer(timer); 
Thread.Sleep(100); 
Assert.IsTrue(cut.SomethingImportantHappened); 
}
Set timeout/interval to 1 
Also seen with a very small number (or zero) 
Usually done when need to wait for next tick/timeout 
× Time based == fragile/inconsistent test 
× Hard to investigate failures 
× Usually comes with Thread.Sleep
Fake & Sync 
Test 
Code under Test 
Fake 
Logic 
Assert Results
Using Typemock Isolator to fake Timer 
[TestMethod, Isolated] 
public void ThisIsAGoodTest() 
{ 
var fakeTimer = Isolate.Fake.Instance<Timer>(); 
var cut = new ClassWithTimer(fakeTimer); 
var fakeEventArgs = Isolate.Fake.Instance<ElapsedEventArgs>(); 
Isolate.Invoke.Event( 
() => fakeTimer.Elapsed += null, this, fakeEventArgs); 
Assert.IsTrue(cut.SomethingImportantHappened); 
}
Unfortunately not everything can be faked 
• Mocking tool limitation (example: inheritance based) 
• Programming language attributes 
• Special cases (example: MSCorlib) 
Solution – wrap the unfakeable 
× Problem – it requires code change
Wrapping Threadpool 
public class ThreadPoolWrapper 
{ 
public static void QueueUserWorkItem(WaitCallback callback) 
{ 
ThreadPool.QueueUserWorkItem(callback); 
} 
}
Wrapping Threadpool 
public class ClassWithWrappedThreadpool 
{ 
public void RunInThread() 
{ 
ThreadPool.QueueUserWorkItem(_ => 
{ 
SomethingImportantHappened = true; 
}); 
} 
public bool SomethingImportantHappened { get; private set; } 
} 
ThreadPoolWrapper.QueueUserWorkItem(_ =>
Testing with new ThreadpoolWrapper 
[TestClass] 
public class WorkingWithThreadpool 
{ 
[TestMethod, Isolated] 
public void UsingWrapperTest() 
{ 
Isolate.WhenCalled(() => ThreadPoolWrapper.QueueUserWorkItem(null)) 
.DoInstead(ctx => ((WaitCallback)ctx.Parameters[0]).Invoke(null)); 
var cut = new ClassWithWrappedThreadpool(); 
cut.RunInThread(); 
Assert.IsTrue(cut.SomethingImportantHappened); 
} 
}
How can we test that an 
asynchronous operation 
never happens?
Test Code Under Test 
Is Test 
Async 
Deterministic Assert Results 
Run in sync
Another day – another class to test 
public void Start() 
{ 
_cancellationTokenSource = new CancellationTokenSource(); 
Task.Run(() => 
{ 
var message = _messageBus.GetNextMessage(); 
if(message == null) 
return; 
// Do work 
if (OnNewMessage != null) 
{ 
OnNewMessage(this, EventArgs.Empty); 
} 
}, _cancellationTokenSource.Token); 
}
Switching code to “test mode” 
• Dependency injection 
• Preprocessor directives 
• Pass delegate 
• Other
Run in single thread patterns 
• Fake & Sync 
• Async code  sync test
Synchronized run patterns 
When you have to run a concurrent test in a predictable way
The Signaled pattern 
Start 
Run 
Code under test 
Signal 
Fake object 
Call 
Wait Assert result
Using the Signaled pattern 
public void DiffcultCalcAsync(int a, int b) 
{ 
Task.Run(() => 
{ 
Result = a + b; 
_otherClass.DoSomething(Result); 
}); 
}
Using the Signaled pattern 
[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); 
}
Busy Assert 
Start 
Run 
Code under 
test 
Assert or 
Timeout
Busy assertion 
[TestMethod] 
public void DifficultCalculationTest() 
{ 
var cut = new ClassWithAsyncOperation(); 
cut.RunAsync(2, 3); 
AssertHelper.BusyAssert(() => cut.Result == 5, 50, 100, "fail!"); 
}
Synchronized test patterns 
× 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
Unit testing for concurrency issues 
Because concurrency (and async) needs to be tested as well
Test for async 
Start Method under test 
Fake object 
wait 
Assert results
Test for Deadlock 
Start 
Execute 
Code under 
test 
Thread 
WWaaitit Assert result 
Call Thread
[TestMethod, Timeout(5000)] 
public void CheckForDeadlock() 
{ 
var fakeDependency1 = A.Fake<IDependency>(); 
var fakeDependency2 = A.Fake<IDependency>(); 
var waitHandle = new ManualResetEvent(false); 
var cut = new ClassWithDeadlock(fakeDependency1, fakeDependency2); 
A.CallTo(() => fakeDependency1.Call()).Invokes(() => waitHandle.WaitOne()); 
A.CallTo(() => fakeDependency2.Call()).Invokes(() => waitHandle.WaitOne()); 
var t1 = RunInOtherThread(() => cut.Call1Then2()); 
var t2 = RunInOtherThread(() => cut.Call2Then1()); 
waitHandle.Set(); 
t1.Join(); 
t2.Join(); 
} 
T1 
T2 
L1 
L2
Avoid 
Concurrency 
Humble 
object 
Test before – 
test after 
Run in single 
thread 
Fake & Sync 
Async in 
production - 
sync in test 
Synchronize 
test 
The Signaled 
pattern 
Busy 
assertion 
Concurrency 
tests 
Test for 
Deadlock 
Test for non 
blocking 
Concurrent unit testing patterns
Conclusion 
It is possible to test concurrent code 
Avoid concurrency 
Run in single thread 
Synchronize test
Dror Helper 
C: 972.50.7668543 
e: drorh@codevalue.net 
B: blog.drorhelper.com 
w: www.codevalue.net

More Related Content

PDF
Power system safety and protection
PPT
Basics of Hydro power plant
PPT
performance evaluation of desilting devices
PDF
ACERO DIMENSIONADO.pdf
PPTX
Cooling Tower Fan Cylinder Characteristics
PPTX
Tacoma Narrows Bridge - 1940
PDF
Pre-Feasibility Study of a 1000 MW Pumped Storage Plant in Saudi Arabia
PPTX
Visual Studio tricks every dot net developer should know
Power system safety and protection
Basics of Hydro power plant
performance evaluation of desilting devices
ACERO DIMENSIONADO.pdf
Cooling Tower Fan Cylinder Characteristics
Tacoma Narrows Bridge - 1940
Pre-Feasibility Study of a 1000 MW Pumped Storage Plant in Saudi Arabia
Visual Studio tricks every dot net developer should know

Similar to Unit testing patterns for concurrent code (20)

PPTX
Unit testing patterns for concurrent code
PDF
Testing concurrent java programs - Sameer Arora
PPTX
Concurrency - responsiveness in .NET
PPTX
Principles and patterns for test driven development
PPTX
Building unit tests correctly with visual studio 2013
PPTX
Unit testing
PPTX
Async await
PPTX
Building unit tests correctly
PDF
JUnit PowerUp
PDF
Async await...oh wait!
PPTX
Rc2010 tdd
PPTX
Writing Good Tests
PPTX
History of asynchronous in .NET
PPTX
The secret unit testing tools no one ever told you about
PPTX
Async/Await
PPTX
Type mock isolator
PDF
Test and Behaviour Driven Development (TDD/BDD)
PPTX
Tdd & unit test
PPTX
Unit testing basics with NUnit and Visual Studio
PDF
Unit testing basic
Unit testing patterns for concurrent code
Testing concurrent java programs - Sameer Arora
Concurrency - responsiveness in .NET
Principles and patterns for test driven development
Building unit tests correctly with visual studio 2013
Unit testing
Async await
Building unit tests correctly
JUnit PowerUp
Async await...oh wait!
Rc2010 tdd
Writing Good Tests
History of asynchronous in .NET
The secret unit testing tools no one ever told you about
Async/Await
Type mock isolator
Test and Behaviour Driven Development (TDD/BDD)
Tdd & unit test
Unit testing basics with NUnit and Visual Studio
Unit testing basic
Ad

More from Dror Helper (20)

PPTX
Debugging with visual studio beyond 'F5'
PPTX
From clever code to better code
PPTX
From clever code to better code
PPTX
A software developer guide to working with aws
PPTX
The secret unit testing tools no one has ever told you about
PPTX
The role of the architect in agile
PDF
Harnessing the power of aws using dot net core
PPTX
Developing multi-platform microservices using .NET core
PPTX
Harnessing the power of aws using dot net
PPTX
Secret unit testing tools no one ever told you about
PPTX
C++ Unit testing - the good, the bad & the ugly
PPTX
Working with c++ legacy code
PPTX
Secret unit testing tools
PPTX
Electronics 101 for software developers
PPTX
Navigating the xDD Alphabet Soup
PPTX
Who’s afraid of WinDbg
PPTX
Designing with tests
PPTX
Writing clean code in C# and .NET
PPTX
Using FakeIteasy
PPTX
Battle of The Mocking Frameworks
Debugging with visual studio beyond 'F5'
From clever code to better code
From clever code to better code
A software developer guide to working with aws
The secret unit testing tools no one has ever told you about
The role of the architect in agile
Harnessing the power of aws using dot net core
Developing multi-platform microservices using .NET core
Harnessing the power of aws using dot net
Secret unit testing tools no one ever told you about
C++ Unit testing - the good, the bad & the ugly
Working with c++ legacy code
Secret unit testing tools
Electronics 101 for software developers
Navigating the xDD Alphabet Soup
Who’s afraid of WinDbg
Designing with tests
Writing clean code in C# and .NET
Using FakeIteasy
Battle of The Mocking Frameworks
Ad

Recently uploaded (20)

PPTX
Cloud computing and distributed systems.
PPT
Teaching material agriculture food technology
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
cuic standard and advanced reporting.pdf
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Machine learning based COVID-19 study performance prediction
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Cloud computing and distributed systems.
Teaching material agriculture food technology
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
cuic standard and advanced reporting.pdf
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
MIND Revenue Release Quarter 2 2025 Press Release
“AI and Expert System Decision Support & Business Intelligence Systems”
Building Integrated photovoltaic BIPV_UPV.pdf
Reach Out and Touch Someone: Haptics and Empathic Computing
Chapter 3 Spatial Domain Image Processing.pdf
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
The Rise and Fall of 3GPP – Time for a Sabbatical?
Dropbox Q2 2025 Financial Results & Investor Presentation
Agricultural_Statistics_at_a_Glance_2022_0.pdf
20250228 LYD VKU AI Blended-Learning.pptx
Review of recent advances in non-invasive hemoglobin estimation
Unlocking AI with Model Context Protocol (MCP)
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Machine learning based COVID-19 study performance prediction
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...

Unit testing patterns for concurrent code

  • 1. Unit testing patterns for concurrent code Dror Helper [email protected] | @dhelper | http://blog.drorhelper.com Examples: https://github.com/dhelper/ConcurrentUnitTesting
  • 2. About.ME • Senior consultant @CodeValue • Developing software (professionally) since 2002 • Mocking code since 2008 • Test Driven Developer • Blogger: http://blog.drorhelper.com
  • 3. We live in a concurrent world! The free lunch is over! • Multi-core CPUs are the new standard • New(er) language constructs • New(ish) languages
  • 4. Meanwhile in the unit testing “world” [Test] public void AddTest() { var cut = new Calculator(); var result = cut.Add(2, 3); Assert.AreEqual(5, result); }
  • 5. The dark art of concurrent code Several actions at the same time Hard to follow code path Non deterministic execution
  • 6. Good unit tests must be: • Trustworthy – Consistent results – Only fail due to bug or requirement change • Maintainable – Robust – Easy to refactor – Simple to update • Readable
  • 7. Concurrency test “smells” × Inconsistent results × Untraceable fail × Long running tests × Test freeze
  • 8. How can we test this method public void Start() { _worker = new Thread(() => { while (_isAlive) { Thread.Sleep(1000); var msg = _messageProvider.GetNextMessage(); //Do stuff LastMessage = msg; } }); _worker.Start(); }
  • 9. Testing start take #1 [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. Test smell - “Sleep” in test × Time based - fail/pass inconsistently × Test runs for too long × Hard to investigate failures
  • 11. “In concurrent programming if something can happen, then sooner or later it will, probably at the most inconvenient moment” Paul Butcher – Seven concurrency models in seven weeks
  • 12. Testing start take #2 [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. Pattern #1 – humble object pattern We extract all the logic from the hard-to-test component into a component that is testable via synchronous tests. http://xunitpatterns.com/Humble%20Object.html Perform Action Code under Test Start Humble object Async Perform action Assert Result Production Code
  • 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. And finally – the test [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. Concurrency as part of program flow public class MessageManager { private IMesseageQueue _messeageQueue; public void CreateMessage(string message) { // Here Be Code! _messeageQueue.Enqueue(message); } } public class MessageClient { private IMesseageQueue _messeageQueue; public string LastMessage { get; set; } private void OnMessage(object sender, EventArgs e) { // Here Be Code! LastMessage = e.Message; } }
  • 19. Test before – Test After Start Code under test Async Logic2 Assert results Logic1 Start Logic1 Fake Assert Results Start Fake Logic2 Assert Results
  • 20. Testing Flow part 1 [TestMethod] public void AddNewMessageProcessedMessageInQueue() { var messeageQueue = new AsyncMesseageQueue(); var manager = new MessageManager(messeageQueue); manager.CreateNewMessage("a new message"); Assert.AreEqual(1, messeageQueue.Count); }
  • 21. Testing Flow part 2 [TestMethod] public void QueueRaisedNewMessageEventClientProcessEvent() { var messeageQueue = new AsyncMesseageQueue(); var client = new MessageClient(messeageQueue); client.OnMessage(null, new MessageEventArgs("A new message")); Assert.AreEqual("A new message", client.LastMessage); }
  • 22. Avoid concurrency Patterns The best possible solution No concurrency == no problems × Do not test some of the code × Not applicable in every scenario
  • 23. How can we test this class? public class ClassWithTimer { private Timer _timer; public ClassWithTimer(Timer timer) { _timer = timer; _timer.Elapsed += OnTimerElapsed; _timer.Start(); } private void OnTimerElapsed(object sender, ElapsedEventArgs e) { SomethingImportantHappened = true; } public bool SomethingImportantHappened { get; private set; } }
  • 24. Not a good idea [TestMethod] public void ThisIsABadTest() { var timer = new Timer(1); var cut = new ClassWithTimer(timer); Thread.Sleep(100); Assert.IsTrue(cut.SomethingImportantHappened); }
  • 25. Set timeout/interval to 1 Also seen with a very small number (or zero) Usually done when need to wait for next tick/timeout × Time based == fragile/inconsistent test × Hard to investigate failures × Usually comes with Thread.Sleep
  • 26. Fake & Sync Test Code under Test Fake Logic Assert Results
  • 27. Using Typemock Isolator to fake Timer [TestMethod, Isolated] public void ThisIsAGoodTest() { var fakeTimer = Isolate.Fake.Instance<Timer>(); var cut = new ClassWithTimer(fakeTimer); var fakeEventArgs = Isolate.Fake.Instance<ElapsedEventArgs>(); Isolate.Invoke.Event( () => fakeTimer.Elapsed += null, this, fakeEventArgs); Assert.IsTrue(cut.SomethingImportantHappened); }
  • 28. Unfortunately not everything can be faked • Mocking tool limitation (example: inheritance based) • Programming language attributes • Special cases (example: MSCorlib) Solution – wrap the unfakeable × Problem – it requires code change
  • 29. Wrapping Threadpool public class ThreadPoolWrapper { public static void QueueUserWorkItem(WaitCallback callback) { ThreadPool.QueueUserWorkItem(callback); } }
  • 30. Wrapping Threadpool public class ClassWithWrappedThreadpool { public void RunInThread() { ThreadPool.QueueUserWorkItem(_ => { SomethingImportantHappened = true; }); } public bool SomethingImportantHappened { get; private set; } } ThreadPoolWrapper.QueueUserWorkItem(_ =>
  • 31. Testing with new ThreadpoolWrapper [TestClass] public class WorkingWithThreadpool { [TestMethod, Isolated] public void UsingWrapperTest() { Isolate.WhenCalled(() => ThreadPoolWrapper.QueueUserWorkItem(null)) .DoInstead(ctx => ((WaitCallback)ctx.Parameters[0]).Invoke(null)); var cut = new ClassWithWrappedThreadpool(); cut.RunInThread(); Assert.IsTrue(cut.SomethingImportantHappened); } }
  • 32. How can we test that an asynchronous operation never happens?
  • 33. Test Code Under Test Is Test Async Deterministic Assert Results Run in sync
  • 34. Another day – another class to test public void Start() { _cancellationTokenSource = new CancellationTokenSource(); Task.Run(() => { var message = _messageBus.GetNextMessage(); if(message == null) return; // Do work if (OnNewMessage != null) { OnNewMessage(this, EventArgs.Empty); } }, _cancellationTokenSource.Token); }
  • 35. Switching code to “test mode” • Dependency injection • Preprocessor directives • Pass delegate • Other
  • 36. Run in single thread patterns • Fake & Sync • Async code  sync test
  • 37. Synchronized run patterns When you have to run a concurrent test in a predictable way
  • 38. The Signaled pattern Start Run Code under test Signal Fake object Call Wait Assert result
  • 39. Using the Signaled pattern public void DiffcultCalcAsync(int a, int b) { Task.Run(() => { Result = a + b; _otherClass.DoSomething(Result); }); }
  • 40. Using the Signaled pattern [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); }
  • 41. Busy Assert Start Run Code under test Assert or Timeout
  • 42. Busy assertion [TestMethod] public void DifficultCalculationTest() { var cut = new ClassWithAsyncOperation(); cut.RunAsync(2, 3); AssertHelper.BusyAssert(() => cut.Result == 5, 50, 100, "fail!"); }
  • 43. Synchronized test patterns × 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
  • 44. Unit testing for concurrency issues Because concurrency (and async) needs to be tested as well
  • 45. Test for async Start Method under test Fake object wait Assert results
  • 46. Test for Deadlock Start Execute Code under test Thread WWaaitit Assert result Call Thread
  • 47. [TestMethod, Timeout(5000)] public void CheckForDeadlock() { var fakeDependency1 = A.Fake<IDependency>(); var fakeDependency2 = A.Fake<IDependency>(); var waitHandle = new ManualResetEvent(false); var cut = new ClassWithDeadlock(fakeDependency1, fakeDependency2); A.CallTo(() => fakeDependency1.Call()).Invokes(() => waitHandle.WaitOne()); A.CallTo(() => fakeDependency2.Call()).Invokes(() => waitHandle.WaitOne()); var t1 = RunInOtherThread(() => cut.Call1Then2()); var t2 = RunInOtherThread(() => cut.Call2Then1()); waitHandle.Set(); t1.Join(); t2.Join(); } T1 T2 L1 L2
  • 48. Avoid Concurrency Humble object Test before – test after Run in single thread Fake & Sync Async in production - sync in test Synchronize test The Signaled pattern Busy assertion Concurrency tests Test for Deadlock Test for non blocking Concurrent unit testing patterns
  • 49. Conclusion It is possible to test concurrent code Avoid concurrency Run in single thread Synchronize test
  • 50. Dror Helper C: 972.50.7668543 e: [email protected] B: blog.drorhelper.com w: www.codevalue.net

Editor's Notes

  • #6:  applications will increasingly need to be concurrent if they want to fully exploit CPU throughput gains
  • #8: If it’s unpredictable – how can we test it?
  • #16: Image by Mark http://upload.wikimedia.org/wikipedia/commons/0/06/Stay_Alive_and_Avoid_Zombies.png
  • #17: Humble object becomes thin layer Logic is accessible via new object
  • #22: Message processing example
  • #23: Message processing example
  • #24: Message processing example
  • #25: Message processing example
  • #35: Some things cannot be tested