3

我正在使用套接字服务器并尝试根据测试驱动开发模式工作。

套接字创建方法有一个工作测试,但我的测试挂起,因为我有一个 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,...
}

如果我评论所有的手动重置事件,测试通过就好了,但这不是解决方案,因为服务器尝试不断创建重复的套接字;-)

对我有什么提示吗?将不胜感激!

问候,马丁

4

2 回答 2

0

问题是,你想准确地测试什么?

如果测试只是期望StartListening调用该方法listener.BeginAcceptTcpClient,那么最简单的解决方案是ManualResetEvent在 SUT 中注入和模拟您的同步对象 (),就像您使用具有非阻塞模拟的侦听器所做的那样。

当您想测试阻塞时,您必须在测试中启动另一个线程,然后在经过一段安全的等待时间后调用listener.BeginAcceptTcpClientManualResetEvent 信号- 比如说两秒钟 - 但这甚至可能还不够,所以您的测试结果可能不是确定性的格热戈兹评论道。

线程和单元测试可能会变得有些复杂。

于 2012-11-08T08:38:24.747 回答
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方法以将 aManualResetEvent作为参数,并SetallDone.WaitOne();. 您可以通过以下方式进行设置:

ManualResetEvent resetEvent = new ManualResetEvent(false);
NetworkChannel networkChannel = new NetworkChannel();
networkChannel.StartListening(resetEvent);

resetEvent.WaitOne();
于 2012-11-08T07:38:19.907 回答