Test driving interactions using Async and Await

September 15, 2012

From time to time I encounter a problem that seems pretty straight-forward to implement without TDD, but very cumbersome to do with TDD. Most of these cases have something in common: they describe interactions between the tested component and external parties. These external parties can be either the user, external system, or any kind of communication protocol. Here’s the simplest example I could think of:

clip_image001

This is a simple console application that asks the user for his name, and then greets him with “Hello, “, followed by his name.

Writing this application takes exactly 3 lines of code to write without TDD:

   1: Console.WriteLine("Please enter your name:");

   2: var name = Console.ReadLine();

   3: Console.WriteLine("Hello, " + name);

Single Responsibility

This is a very simplistic piece of code, and there’s no point in making it more complicated than it is if this program is exactly what you need. But often when you have something similar in a real-world application, such 3 lines of code are considered “bad” because it has 2 distinct responsibilities:

  1. Controlling the interaction flow (print message, read input, then print another message)
  2. Reading and Writing from the Console

If a console application is developed in TDD, these 2 responsibilities will normally be separated into 2 classes (or even 3: the logic, input handler and output handler)

This extra “complexity” is not what bothers me, as this is what improves the code quality and make it easier to maintain, at least in the long run. I’ll get to the complexity that bothers me in testing interactions later.

But for now, just for comparison, let’s develop a pretty similar app but that doesn’t involve interaction logic, (i.e. it doesn’t wait for any external input). This app would be… the famous “Hello, world” application!

So let’s TDD “Hello, world”:

   1: [TestMethod]

   2: public void RunDisplaysHelloWorld()

   3: {

   4:     var outputHandler = new MockOutputHandler();

   5:     var helloWorldLogic = new HelloWorldLogic(outputHandler);

   6:     helloWorldLogic.Execute();

   7:     Assert.AreEqual("Hello, world", outputHandler.GetOutput());

   8: }

No let’s create the necessary classes and methods so it can compile:

   1: public class HelloWorldLogic

   2: {

   3:     public HelloWorldLogic(IOutputHandler outputHandler)

   4:     {

   5:         throw new NotImplementedException();

   6:     }

   7:     public void Execute()

   8:     {

   9:         throw new NotImplementedException();

  10:     }

  11: }

  12: public interface IOutputHandler

  13: {

  14: }

  15: public class MockOutputHandler : IOutputHandler

  16: {

  17:     public string GetOutput()

  18:     {

  19:         throw new NotImplementedException();

  20:     }

  21: }

Once you’ll get rid of all the NotImplementedException failues, you’ll get the following failure:

Assert.AreEqual failed. Expected:<Hello, world>. Actual:<(null)>. 

So now we need to implement it in order to make the test pass:

   1: public class HelloWorldLogic

   2: {

   3:     private readonly IOutputHandler _outputHandler;

   4:     public HelloWorldLogic(IOutputHandler outputHandler)

   5:     {

   6:         _outputHandler = outputHandler;

   7:     }

   8:     public void Execute()

   9:     {

  10:         _outputHandler.Output("Hello, world");

  11:     }

  12: }

  13: public interface IOutputHandler

  14: {

  15:     void Output(string output);

  16: }

  17: public class MockOutputHandler : IOutputHandler

  18: {

  19:     private string _output;

  20:     public string GetOutput()

  21:     {

  22:         return _output;

  23:     }

  24:     public void Output(string output)

  25:     {

  26:         _output = output;

  27:     }

  28: }

Now if we want to use this class with a Console output, we need to create another class that implements IOutputHandler and directs the output to the Console (I tend not to TDD classes that interact with OS, hardware etc, though you can do it using Microsoft Moles or its newer version that’s now called Microsoft Fakes, or any mocking framework that can override non-virtual functions)

   1: public class ConsoleOutput : IOutputHandler

   2: {

   3:     public void Output(string output)

   4:     {

   5:         Console.WriteLine(output);

   6:     }

   7: }

And the Main method should look like this:

   1: public static void Main()

   2: {

   3:     new HelloWorldLogic(new ConsoleOutput()).Execute();

   4: }

Even though it might look to you way to much complicated for a “Hello, World” application (and frankly it is…), this is just because it’s just a “Hello, World” application. But if you’re writing a real-world application which needs to write some output, it makes perfect sense to isolate the physical output means from the rest of the logic in this manner. Consider how easy it is now to change this application to display the output in any other mean (e.g. MessageBox, Log file, etc).

The complexity in testing interactions

So back to our GreetUser application:

I’d like to be able to write something like this:

   1: [TestMethod]

   2: public void GreetUserDisplaysHelloWithTheUserName()

   3: {

   4:     var inputMock = new MockInputHandler();

   5:     var outputMock = new MockOutputHandler();

   6:     var greeter = new Greeter(inputMock, outputMock);

   7:     greeter.Greet();

   8:     Assert.AreEqual("Please enter your name:", outputMock.GetOutput());

   9:     inputMock.Write("Arnon");

  10:     Assert.AreEqual("Hello, Arnon", outputMock.GetOutput());

  11: }

Let’s suppose I do so, then first I should create the following types in order for it to compile:

   1: public class Greeter

   2: {

   3:     public Greeter(IInputHandler inputHandler, IOutputHandler outputHandler)

   4:     {

   5:         throw new NotImplementedException();

   6:     }

   7:     public void Greet()

   8:     {

   9:         throw new NotImplementedException();

  10:     }

  11: }

  12: public interface IInputHandler

  13: {

  14: }

  15: public class MockInputHandler : IInputHandler

  16: {

  17:     public void Write(string line)

  18:     {

  19:         throw new NotImplementedException();

  20:     }

  21: }

After getting rid of all the NotImplementedExceptions, we’ll get the following failure:

Assert.AreEqual failed. Expected:<Please enter your name:>. Actual:<(null)>

Similar to what we had in the “Hello, World” example.

So let’s do it:

   1: public class Greeter

   2: {

   3:     private readonly IOutputHandler _outputHandler;

   4:     public Greeter(IInputHandler inputHandler, IOutputHandler outputHandler)

   5:     {

   6:         _outputHandler = outputHandler;

   7:     }

   8:     public void Greet()

   9:     {

  10:         _outputHandler.Output("Please enter your name:");

  11:     }

  12: }

Now, we’re getting the following failure:

Assert.AreEqual failed. Expected:<Hello, Arnon>. Actual:<Please enter your name:>

Well, obviously we’re getting the first output line because we didn’t write the 2nd line yet. So let’s call Write once more!

But… wait a minute… we first need to build the string we need to send to the output, and for that we first need to read the user name from the input. So let’s do that:

   1: public class Greeter

   2: {

   3:     private readonly IInputHandler _inputHandler;

   4:     private readonly IOutputHandler _outputHandler;

   5:     public Greeter(IInputHandler inputHandler, IOutputHandler outputHandler)

   6:     {

   7:         _inputHandler = inputHandler;

   8:         _outputHandler = outputHandler;

   9:     }

  10:     public void Greet()

  11:     {

  12:         _outputHandler.Output("Please enter your name:");

  13:         var name = _inputHandler.GetInput();

  14:         _outputHandler.Output("Hello, " + name);

  15:     }

  16: }

And now we need to add and implement GetInput in order to compile:

   1: public interface IInputHandler

   2: {

   3:     string GetInput();

   4: }

   5: public class MockInputHandler : IInputHandler

   6: {

   7:     private string _line;

   8:     public void Write(string line)

   9:     {

  10:         _line = line;

  11:     }

  12:     public string GetInput()

  13:     {

  14:         return _line;

  15:     }

  16: }

Let’s run the test now!

Sad smileSad smileSad smileSad smile… Even though it seems that we’ve completed writing all the necessary code, our test still fails with the following error:

Assert.AreEqual failed. Expected:<Please enter your name:>. Actual:<Hello, >.

Hmmm… a little bit of debugging will reveal that Greeter.Greet() calls MockInput.GetInput() before the test calls MockInput.Write(“Arnon”). That’s because Greeter.Greet() performs its work and returns synchronously.

You’re welcome to play with it, but as far as I can tell, any solution to this problem requires the test to be changed, and not only the production code. For example, one way to solve it is to change the test to:

   1: [TestMethod]

   2: public void GreetUserDisplaysHelloWithTheUserName()

   3: {

   4:     var inputMock = new MockInputHandler();

   5:     var outputMock = new MockOutputHandler();

   6:     var completed = false;

   7:     outputMock.OnOutput = output1 =>

   8:         {

   9:             Assert.AreEqual("Please enter your name:", output1);

  10:             inputMock.Write("Arnon");

  11:             outputMock.OnOutput = output2 =>

  12:                 {

  13:                     Assert.AreEqual("Hello, Arnon", output2);

  14:                     completed = true;

  15:                 };

  16:         };

  17:     var greeter = new Greeter(inputMock, outputMock);

  18:     greeter.Greet();

  19:     Assert.IsTrue(completed);

  20: }

  21: public class MockOutputHandler : IOutputHandler

  22: {

  23:     public Action<string> OnOutput { get; set; }

  24:     public void Output(string output)

  25:     {

  26:         OnOutput(output);

  27:     }

  28: }

If we would have started with this test, we would probably end up with the same implementation of our Greeter class (and the test would pass). The way this test is written makes sense because it describes the interactions: it describes the flow by specifying what should happen in response to each output. But nonetheless this test is much less readable than the one we wrote before and didn’t work.

Async and await comes to help!

Fortunately, C# 4.5 helps us write test code that looks similar to what we wanted to write, but works pretty much the same as what we eventually wrote:

   1: [TestMethod]

   2: public async Task GreetUser()

   3: {

   4:     var mockInput = new MockInputHandler();

   5:     var mockOutput = new MockOutputHandler();

   6:     var greeter = new Greeter(mockInput, mockOutput);

   7:     greeter.GreetUser();

   8:     Assert.AreEqual("Please enter your name:", await mockOutput.GetOutput());

   9:     mockInput.Write("Arnon");

  10:     Assert.AreEqual("Hello, Arnon", await mockOutput.GetOutput());

  11: }

Note that we’re using the await keyword to wait for the output before we can read it, and only then writes the input, and then we’re awaiting the 2nd output line to be written before we assert that it returns the expected result.

Because we’re using the await keyword in the test method, we must also use the async modifier on the method. MS-Test only supports async Task methods (as opposed to async void or async Task<T>), but it doesn’t bother us in any way, and MS-Test 2012 takes care of executing the test and waiting for it to complete.

So let’s create the classes and interfaces we need to compile:

   1: class MockInputHandler: IInputHandler

   2: {

   3: }

   4: class MockOutputHandler : IOutputHandler

   5: {

   6:     internal Task<string> GetOutput()

   7:     {

   8:         throw new NotImplementedException();

   9:     }

  10: }

  11: interface IInputHandler

  12: {

  13: }

  14: interface IOutputHandler

  15: {

  16: }

  17: class Greeter

  18: {

  19:     public Greeter(IInputHandler inputHandler, IOutputHandler outputHandler)

  20:     {

  21:         throw new NotImplementedException();

  22:     }

  23:     internal void GreetUser()

  24:     {

  25:         throw new NotImplementedException();

  26:     }

  27: }

And after we get rid of all the failures one by one, we’ll end up with the following code:

   1: class MockInputHandler: IInputHandler

   2: {

   3:     private TaskCompletionSource<string> _completionSource = new TaskCompletionSource<string>();

   4:     internal void Write(string text)

   5:     {

   6:         _completionSource.SetResult(text);

   7:     }

   8:     public Task<string> GetInput()

   9:     {

  10:         return _completionSource.Task;

  11:     }

  12: }

  13: interface IInputHandler

  14: {

  15:     Task<string> GetInput();

  16: }

  17: class MockOutputHandler : IOutputHandler

  18: {

  19:     private TaskCompletionSource<string> _completionSource = new TaskCompletionSource<string>();

  20:     internal Task<string> GetOutput()

  21:     {

  22:         var result = _completionSource.Task;

  23:         _completionSource = new TaskCompletionSource<string>();

  24:         return result;

  25:     }

  26:     public void Output(string line)

  27:     {

  28:         _completionSource.SetResult(line);

  29:     }

  30: }

  31: interface IOutputHandler

  32: {

  33:     void Output(string line);

  34: }

  35: class Greeter

  36: {

  37:     private IInputHandler _inputHandler;

  38:     private IOutputHandler _outputHandler;

  39:     public Greeter(IInputHandler inputHandler, IOutputHandler outputHandler)

  40:     {

  41:         _inputHandler = inputHandler;

  42:         _outputHandler = outputHandler;

  43:     }

  44:     internal async void GreetUser()

  45:     {

  46:         _outputHandler.Output("Please enter your name:");

  47:         var name = await _inputHandler.GetInput();

  48:         _outputHandler.Output("Hello, " + name);

  49:     }

  50: }

Conclusion

Even though we had to change the API of our class to return Task<string> it’s only natural as we’re dealing with asynchronous interaction logic, and therefore it makes sense to make the Greet method asynchronous. Think how easy it will be to re-use the Greeter class with a WPF/Windows Forms/(anyone said Silverlight?!) application instead of a console application – even though a Console application can completes tasks on a different thread while a Windows application always uses the UI thread…

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

one comment

  1. http://gucci-japan.magicgnss.comAugust 12, 2013 ב 18:07

    園博会再現江南四大名園の細部に会ってスタイル組図 グッチ 財布

    Reply