gpt4 book ai didi

c# - 如何正确地对异步套接字连接操作进行单元测试并避免 Thread.Sleep?

转载 作者:太空宇宙 更新时间:2023-11-03 22:32:16 25 4
gpt4 key购买 nike

我有一个名为 TcpConnector 的类,它会在与端点的连接成功完成时引发一个事件。

这是缩短的实现:

public class TcpConnectorEventArgs : EventArgs
{
public Exception EventException { get; set; }
[...]
}

public class TcpConnector
{
public event EventHandler<TcpConnectorEventArgs> EventDispatcher;

public void BeginConnect(IPEndPoint endpoint, int timeoutMillis)
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

var ipcState = new IpcState()
{
IpcSocket = socket,
IpcEndpoint = endpoint,
IpcTimeoutMillis = timeoutMillis
};

try
{
ipcState.IpcSocket.BeginConnect(ipcState.IpcEndpoint, HandleConnect, ipcState);
}
catch (Exception ex)
{
var tcpConnectorEventArgs = new TcpConnectorEventArgs()
{
EventSocket = ipcState.IpcSocket,
EventEndPoint = ipcState.IpcEndpoint,
EventType = TcpConnectorEventTypes.EventConnectFailure,
EventException = ex
};

EventDispatcher?.Invoke(this, tcpConnectorEventArgs);
}
}

private void HandleConnect(IAsyncResult asyncResult)
{
var ipcState = asyncResult.AsyncState as IpcState;

if (ipcState == null)
{
return;
}

try
{
var result = asyncResult.AsyncWaitHandle.WaitOne(ipcState.IpcTimeoutMillis, true);

if (result)
{
ipcState.IpcSocket.EndConnect(asyncResult);

var tcpConnectorEventArgs = new TcpConnectorEventArgs()
{
EventSocket = ipcState.IpcSocket,
EventEndPoint = ipcState.IpcEndpoint,
EventType = TcpConnectorEventTypes.EventConnectSuccess
};

// Raise event with details
EventDispatcher?.Invoke(this, tcpConnectorEventArgs);

// Check cancellation flag if any subscriber wants the
// connection canceled
if (tcpConnectorEventArgs.EventCancel)
{
ipcState.IpcSocket.Close();
}
}
else
{
var tcpConnectorEventArgs = new TcpConnectorEventArgs()
{
EventSocket = ipcState.IpcSocket,
EventEndPoint = ipcState.IpcEndpoint,
EventType = TcpConnectorEventTypes.EventConnectFailure,
EventException = new SocketException(10060) // Connection timed out
};

// Raise event with details about error
EventDispatcher?.Invoke(this, tcpConnectorEventArgs);
}
}
catch (Exception ex)
{
var tcpConnectorEventArgs = new TcpConnectorEventArgs()
{
EventSocket = ipcState.IpcSocket,
EventEndPoint = ipcState.IpcEndpoint,
EventType = TcpConnectorEventTypes.EventConnectFailure,
EventException = ex
};

// Raise event with details about error
EventDispatcher?.Invoke(this, tcpConnectorEventArgs);
}
}
}

这是我正在使用的测试:

[Fact]
[Trait(TraitKey.Category, TraitValue.UnitTest)]
public void Should_Raise_Event_And_Fail_To_Connect()
{
// Arrange
var receivedEvents = new List<TcpConnectorEventArgs>();
var nonListeningPort = 82;
var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), nonListeningPort);
var timeout = 1 * 1000;

var client = new TcpConnector();
client.EventDispatcher += (o, e) => receivedEvents.Add(e);

// Act
client.BeginConnect(endPoint, timeout);
Thread.Sleep(10 * 1000);

// Assert
receivedEvents.Should().HaveCount(1);
receivedEvents[0].EventType.Should().Be(TcpConnectorEventTypes.EventConnectFailure);
receivedEvents[0].EventException.Message.Should().Be("No connection could be made because the target machine actively refused it");
}

由于我的 BeginConnect() 方法是异步执行的,因此不会阻塞调用者,所以我想到了使用 Thread.Sleep() 的愚蠢方法。然而,这感觉不对。

所以问题是:如何“正确”测试此方法?特别是对于正确的超时行为。


我的解决方案

为了完整起见,这就是我的类和测试现在的样子,使用 ConnectAsync()

public class TcpConnector
{
private Socket socket;

//[...]

public async Task ConnectAsync(IPEndPoint endpoint)
{
this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

await this.socket.ConnectAsync(endpoint);
}
}

还有两个示例 xUnit 测试...

[Fact]
[Trait("Category", "UnitTest")]
public async Task Should_Successfully_ConnectAsync()
{
// Arrange
var client = new TcpConnector();
var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);

// Act
var connectTask = client.ConnectAsync(endpoint);
await connectTask;

// Assert
connectTask.IsCompletedSuccessfully.Should().BeTrue();
connectTask.Exception.Should().BeNull();
client.IsConnected().Should().BeTrue();
}

[Fact]
[Trait("Category", "UnitTest")]
public async Task Should_Throw_Exception_If_Port_Unreachable()
{
// Arrange
var client = new TcpConnector();
var nonListeningPort = 81;
var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), nonListeningPort);

// Act & Assert
var connectTask = client.ConnectAsync(endpoint);
Func<Task> func = async () => { await connectTask; };

func.Should().Throw<Exception>();
}

最佳答案

如果你确实使用旧方法,订阅 EventDispatcher,在这个订阅的监听器的实现中发出一个等待句柄信号,让单元测试线程在继续之前等待这个信号。

var signal = new ManualResetEventSlim(false);
var client = new TcpConnector();
client.EventDispatcher += (o, e) => signal.Set();

client.BeginConnect(endPoint, timeout);

signal.WaitOne(); // consider using an overload that takes a timeout

关于c# - 如何正确地对异步套接字连接操作进行单元测试并避免 Thread.Sleep?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56905428/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com