Asynchronous TDD - Test Class blocks
How do I create a unit test to test a class that sits in a loop?
Here's the scenario.
I have a class that is injected with a reference to a serial port.
The class has a method called Send(String data);
This public method invokes an aysnchronous private method to do the actual work.
The class under test (CUT) should do the following when this method is called.
1) split the string into chars.
2) send a char 3) wait for the char to be echoed 4) send the next char (repeat until all chars sent)So after sending the first char the CUT will sit in a loop waiting for an echo until it receives one or times out.
The problem I have that is once the CUT has entered this loop it will block the test class until it times out.
Since I need the test class to send the echo to the CUT im stuck.
To test this I've created a mock serial port and im using NUnit.
The test is below. The idea is that after sending the test str开发者_如何学运维ing I wait for the CUT to respond. Each time the CUT writes a char to the serial port the wait is cancelled and I write an echo to the serial port and the CUT responds to this by sending the next char.
[Test]
public void Test()
{
_serialPort.DataSentEvent += new EventHandler(_serialPort_DataSentEvent);
_completedSync = new ManualResetEvent(false);
_wrapperPort.Send("TEST");
_completedSync.WaitOne(1000); // wait for first char
Assert.AreEqual("T", _serialPort.BufferOUT); //first char sent
_serialPort.BufferIN = "T"; //write the echo
_completedSync.WaitOne(1000); //wait for second char
Assert.AreEqual("E", _serialPort.BufferOUT); //second char sent
//...etc
}
void _serialPort_DataSentEvent(object sender, EventArgs e)
{
_completedSync.Set();
}
But what happens is the CUT blocks as soon as Send("TEST") is called and control only returns to the test class once the CUT has timed out waiting for an echo.
Since the Send method is being completed on another thread why is it blocking the test class?
I think you mix too much implementation details into your test.
You have specified the serial port protocol very precisely, so I would define an interface which describes that protocol:
public interface ISerialPort
{
event EventHandler Echo;
void Send(char c);
}
This interface forces me to send one character at a time. The serial port wrapper will work correctly when it consumes this interface correctly. This approach requires an additional wrapper class for the real serial port. It is this class which needs to handle the async operations in order to implement the interface properly.
With a TestSerialPort
, I can write my test as follows:
[TestMethod]
public void Send_data_as_chars_to_serial_port()
{
const string data = "TEST";
var serialPort = new TestSerialPort();
var wrapperPort = new WrapperPort(serialPort);
wrapperPort.Send(data);
Assert.AreEqual(data, serialPort.Buffer);
Assert.AreEqual(data.Length, serialPort.SendCount);
}
The TestSerialPort
implements the interface, simulates the real serial port behavior, and exposes data the test can assert:
public class TestSerialPort : ISerialPort
{
public string Buffer;
public int SendCount;
public void Send(char c)
{
SendCount++;
Buffer += c;
Echo.Invoke(this, EventArgs.Empty);
}
public event EventHandler Echo;
}
One possible implementation which satisfies the above test:
public class WrapperPort
{
private readonly ISerialPort serialPort;
private Queue<char> buffer;
private char current;
public WrapperPort(ISerialPort serialPort)
{
this.serialPort = serialPort;
serialPort.Echo += OnEcho;
}
public void Send(string data)
{
buffer = new Queue<char>(data);
SendNextCharacter();
}
private void OnEcho(object sender, EventArgs e)
{
SendNextCharacter();
}
private void SendNextCharacter()
{
if (buffer.Any() == false) return;
current = buffer.Dequeue();
serialPort.Send(current);
}
}
精彩评论