我正在使用套接字服务器并尝试按照测试驱动开发模式工作。
套接字创建方法有一个工作测试,但我的测试因为我有一个ManualResetEvent.WaitOne()而挂起,所以我的套接字在创建另一个之前等待连接。
这是要测试的代码块:
ManualResetEvent allDone = new ManualResetEvent(false);
public void StartListening()
{
allDone.Reset();
//Inform on the console that the socket is ready
Console.WriteLine("Waiting for a connection...");
/* Waits for a connection and accepts it. AcceptCallback is called and the actual
* socket passed as AsyncResult
*/
listener.BeginAcceptTcpClient(
new AsyncCallback(AcceptCallback),
listener);
allDone.WaitOne();
}
以下是“networkChannel.StartListening();
中挂起的测试方法[TestMethod]
public void NetworkChannel_IsAcceptingConnectionsAsynchronously()
{
ITcpClient client = MockRepository.GenerateMock<ITcpClient>();
ITcpListener listener = MockRepository.GenerateMock<ITcpListener>();
IAsyncResult asyncResult = MockRepository.GenerateMock<IAsyncResult>();
listener.Expect(x => x.BeginAcceptTcpClient(null, null)).IgnoreArguments().Return(asyncResult);
listener.Expect(x => x.EndAcceptTcpClient(asyncResult)).Return(client);
NetworkChannel networkChannel = new NetworkChannel(listener);
networkChannel.StartListening();
//Some more work... fake callback etc., verfify expectations,...
}
如果我评论所有的manualresetevents,测试通过就好了,但这不是解决方案,因为服务器试图连续创建重复的套接字; - )
对我有任何暗示吗?非常感谢!
此致 马丁
答案 0 :(得分:0)
修改强>
这里的微妙之处在于StartListening
方法会阻塞,所以我原来的方法不起作用,因为我们不能简单地等待它继续完成,但是在我们知道之前我们不能安全地继续NetworkChannel
已经开始倾听。
解决方法是让频道在启动时提供一些通知。我可以想到几个选项:
给NetworkChannel
以下内容:
public delegate void OnStartupComplete();
public event OnStartupComplete StartupComplete;
StartListening
之前的allDone.WaitOne();
方法:
if (this.StartupComplete != null)
{
StartupComplete();
}
通过以下方式设置活动:
ManualResetEvent resetEvent = new ManualResetEvent(false);
NetworkChannel networkChannel = new NetworkChannel();
networkChannel.StartupComplete += () => { resetEvent.Set(); };
networkChannel.StartListening();
resetEvent.WaitOne();
编辑2:
这种方法(以及下面的方法)仍然遇到阻塞StartListening
调用阻止执行后的任何操作的问题 - 比如调用resetEvent.WaitOne();
。我认为你最好的选择是让StartListening
在另一个线程中运行它的代码。结合StartupComplete
事件,我们应该得到我们想要的东西:网络频道在收听时通知我们,resetEvent
确保我们不做任何事情,直到它有。
从单元测试的角度来看,使用另一个线程可能并不令人满意,但我认为从设计的角度来看这是优选的:当我们超越单元测试时,StartListening
像它一样阻塞很可能会带来不便
为此,您可以在StartListening
方法中执行以下操作:
public void StartListening()
{
ThreadPool.QueueUserWorkItem((object state) =>
{
allDone.Reset();
//Inform on the console that the socket is ready
Console.WriteLine("Waiting for a connection...");
/* Waits for a connection and accepts it. AcceptCallback is called and the
* actual socket passed as AsyncResult
*/
listener.BeginAcceptTcpClient(new AsyncCallback(AcceptCallback), listener);
if (this.StartupComplete != null)
{
StartupComplete();
}
allDone.WaitOne();
});
}
或者,您只需修改StartListening
方法即可将ManualResetEvent
作为参数,并在Set
之前调用其allDone.WaitOne();
方法。您可以通过以下方式进行设置:
ManualResetEvent resetEvent = new ManualResetEvent(false);
NetworkChannel networkChannel = new NetworkChannel();
networkChannel.StartListening(resetEvent);
resetEvent.WaitOne();
答案 1 :(得分:0)
问题是,你想要准确测试什么?
如果测试只是希望方法StartListening
调用listener.BeginAcceptTcpClient
,那么最简单的解决方案就是在你的SUT中注入和模拟同步对象(ManualResetEvent
),就像你做的那样非阻塞模拟的监听器。
如果要测试阻塞,则必须在调用listener.BeginAcceptTcpClient
之前在测试中启动另一个线程,该线程在某些安全等待时间后发出ManualResetEvent信号 - 让我们说两秒钟 - 但这甚至可能还不够,所以你的测试结果可能不像Grzegorz评论那样具有确定性。
线程和单元测试可能会有些复杂。