【发布时间】:2022-04-28 22:11:24
【问题描述】:
我想开发一个可测试的 TcpClient / TcpListener 包装器。我希望能够模拟传入和传出的数据。
我想这样做是因为我有更高层的组件应该对网络消息做出反应。出于测试原因,我想模拟(网络)它们。
有人可以给我一个正确的方向吗?
【问题讨论】:
标签: c# unit-testing architecture
我想开发一个可测试的 TcpClient / TcpListener 包装器。我希望能够模拟传入和传出的数据。
我想这样做是因为我有更高层的组件应该对网络消息做出反应。出于测试原因,我想模拟(网络)它们。
有人可以给我一个正确的方向吗?
【问题讨论】:
标签: c# unit-testing architecture
没有。不要模拟 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; }
}
您使用的是Socket、TcpClient 还是NetworkStream 无关紧要。
这里有一些使用流利断言和 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');
}
}
}
最后是测试:
[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>();
}
}
更新(2020-01-23)
今天我只想做一个异步接口:
public interface INetworkClient : IDisposable
{
Task SendAsync(byte[] buffer, int offset, int count);
Task<int> ReceiveAsync(byte[] buffer, int offset, int count);
}
为此,您需要使用SocketAwaitable。
【讨论】:
TcpClient 或Socket 的类,并使其实现该接口。
您可以使用Decorator Pattern
对 TcpServer 重复相同的操作。
【讨论】:
我建议不要构建新的抽象层,但可以使用已经存在的 - Stream。并非所有案件都可以这样处理,但值得考虑。
如果服务采用抽象Stream而不是TcpClient,我们可以在生产代码中提供TcpClient.GetStream(),而对于测试可以是MemoryStream、StringStream或FileStream
【讨论】: