异步TDD - 测试类块

时间:2010-10-07 10:10:31

标签: c# asynchronous tdd

如何创建单元测试来测试位于循环中的类?

这是情景。

我有一个注入了串行端口引用的类。

该类有一个名为Send(String data);

的方法

这个公共方法调用一个同步的私有方法来完成实际的工作。

当调用此方法时,被测试类(CUT)应该执行以下操作。

1)将字符串分成字符。
2)发送一个字母 3)等待char回应
4)发送下一个字符(重复直到所有字符发送)

因此,在发送第一个字符后,CUT将处于循环中等待回显,直到它收到一个或超时。

我遇到的问题是,一旦CUT进入此循环,它将阻止测试类,直到它超时。

因为我需要测试类将回声发送到CUT即时卡住。

为了测试这个,我创建了一个模拟串口,并使用NUnit。

测试如下。我的想法是在发送测试字符串后,我等待CUT响应。每次CUT将字符写入串口时,等待都会被取消,我会向串口写回信,而CUT会通过发送下一个字符来响应。

    [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();
    }

但是,一旦调用Send(“TEST”),控制只返回测试类,一旦CUT超时等待回声,就会发生CUT阻塞。

由于Send方法正在另一个线程上完成,为什么它会阻塞测试类?

1 个答案:

答案 0 :(得分:1)

我认为你在实验中混合了太多的实施细节。

您已经非常精确地指定了串行端口协议,因此我将定义一个描述该协议的接口:

public interface ISerialPort
{
    event EventHandler Echo;

    void Send(char c);
}

此界面强制我一次发送一个字符。正确使用此接口时,串口包装器将正常工作。 这种方法需要额外的实际串口包装类。这个类需要处理异步操作才能正确实现接口。

使用TestSerialPort,我可以按如下方式编写测试:

    [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);
    }

TestSerialPort实现接口,模拟真实的串口行为,并公开测试可以断言的数据:

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;
}

满足上述测试的一种可能的实现方式:

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);
    }
}