如何开发可测试的TcpClient/TcpListener包装器

Grr*_*404 2 c# architecture unit-testing

我想开发一个可测试的TcpClient/TcpListener包装器.我希望能够模拟传入和传出的数据.

我想这样做是因为我有更高级别的组件,应该对网络消息作出反应.出于测试原因,我想模拟(网络)它们.

有人可以给我一个正确的方向吗?

jga*_*fin 7

不.不要模拟ITcpClient和INetworkStream.

网络层只不过是这样的:

public interface INetworkClient : IDisposable
{
    event EventHandler<ReceivedEventArgs> BufferReceived;
    event EventHandler Disconnected;
    void Send(byte[] buffer, int offset, int count);
}

public class ReceivedEventArgs : EventArgs
{
    public ReceivedEventArgs(byte[] buffer)
    {
        if (buffer == null) throw new ArgumentNullException("buffer");
        Buffer = buffer;
        Offset = 0;
        Count = buffer.Length;
    }

    public byte[] Buffer { get; private set; }
    public int Offset { get; private set; }
    public int Count { get; private set; }
}
Run Code Online (Sandbox Code Playgroud)

如果你使用的是a 或者a Socket,那应该没关系.TcpClientNetworkStream

更新,如何编写测试

以下是一些使用流畅断言和NSubstitute的测试示例.

正在测试的类:

public class ReceivedMessageEventArgs : EventArgs
{
    public ReceivedMessageEventArgs(string message)
    {
        if (message == null) throw new ArgumentNullException("message");
        Message = message;
    }

    public string Message { get; private set; }
}

public class SomeService
{
    private readonly INetworkClient _networkClient;
    private string _buffer;

    public SomeService(INetworkClient networkClient)
    {
        if (networkClient == null) throw new ArgumentNullException("networkClient");
        _networkClient = networkClient;
        _networkClient.Disconnected += OnDisconnect;
        _networkClient.BufferReceived += OnBufferReceived;
        Connected = true;
    }

    public bool Connected { get; private set; }

    public event EventHandler<ReceivedMessageEventArgs> MessageReceived = delegate { };

    public void Send(string msg)
    {
        if (msg == null) throw new ArgumentNullException("msg");
        if (Connected == false)
            throw new InvalidOperationException("Not connected");

        var buffer = Encoding.ASCII.GetBytes(msg + "\n");
        _networkClient.Send(buffer, 0, buffer.Length);
    }

    private void OnDisconnect(object sender, EventArgs e)
    {
        Connected = false;
        _buffer = "";
    }

    private void OnBufferReceived(object sender, ReceivedEventArgs e)
    {
        _buffer += Encoding.ASCII.GetString(e.Buffer, e.Offset, e.Count);
        var pos = _buffer.IndexOf('\n');
        while (pos > -1)
        {
            var msg = _buffer.Substring(0, pos);
            MessageReceived(this, new ReceivedMessageEventArgs(msg));

            _buffer = _buffer.Remove(0, pos + 1);
            pos = _buffer.IndexOf('\n');
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后测试:

[TestClass]
public class SomeServiceTests
{
    [TestMethod]
    public void service_triggers_msg_event_when_a_complete_message_is_recieved()
    {
        var client = Substitute.For<INetworkClient>();
        var expected = "Hello world";
        var e = new ReceivedEventArgs(Encoding.ASCII.GetBytes(expected + "\n"));
        var actual = "";

        var sut = new SomeService(client);
        sut.MessageReceived += (sender, args) => actual = args.Message;
        client.BufferReceived += Raise.EventWith(e);

        actual.Should().Be(expected);
    }

    [TestMethod]
    public void Send_should_invoke_Send_of_networkclient()
    {
        var client = Substitute.For<INetworkClient>();
        var msg = "Hello world";

        var sut = new SomeService(client);
        sut.Send(msg);

        client.Received().Send(Arg.Any<byte[]>(), 0, msg.Length + 1);
    }

    [TestMethod]
    public void Send_is_not_allowed_while_disconnected()
    {
        var client = Substitute.For<INetworkClient>();
        var msg = "Hello world";

        var sut = new SomeService(client);
        client.Disconnected += Raise.Event();
        Action actual = () => sut.Send(msg);

        actual.ShouldThrow<InvalidOperationException>();
    }
}
Run Code Online (Sandbox Code Playgroud)


Ana*_*yal 5

您可以使用装饰器模式

  • 制作您自己的仅包装 TcpClient 的类
  • 这个类只是传递对 TcpClient 中函数的调用。
  • 如果涉及创建一个,则此类的重载构造函数可以接受它将包装的实际 tcp 客户端的实例。
  • 提取接口,以便您的新类应该实现接口 ITcpClient
  • 更新所有依赖项以使用新接口 ITcpClient
  • 你的新界面现在是可模拟的,注入你的模拟是合适的并进行测试:)

对 TcpServer 重复相同的操作。