我有一个名为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>();
}
答案 0 :(得分:3)
如果您使用旧方法,请订阅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