单元测试心跳功能

时间:2018-11-16 08:29:15

标签: c# unit-testing nunit client-server integration-testing

我有一个基于C#编码的TCP的客户端-服务器系统。我被要求添加双向心跳,以发现网络中的任何元素是否冻结并且没有响应。

我通过从服务器向所有连接的客户端发送心跳并等待响应来解决了该问题。如果服务器未在2秒内未发送任何心跳(假设服务器已冻结),则响应未按时超时,并且客户端中也存在超时。

所有事情都在内部发生,服务器或客户端也不会公开任何事件(接收心跳,回复心跳会按要求静默发生)

问题是..如何为这种功能创建单元测试或集成测试?

注意:我正在Visual Studio,C#、. Net 4.6.1中进行编码,使用NUnit3进行测试

简短的伪代码示例:

//we have one connector for each connected client
class connector{

    //if the echoReceived timer is not reseted on time it will complain
    Timer echoReceived = new Timer(200ms);
    //Another timer for sending beats
    Timer heartbeatSender = new Timer (1000ms);

    OnClientConnected(Client)
    {        
        echoReceived.elapsed += () => { ShowError("Client did not reply") };

        heartbeatSender.elapsed += () => {
            Send(client, new Message(Heartbeat));
            echoReceived.Enabled = true;
        });
        heartbeatSemder.isEnabled = true;
    }

    OnNewMessageFromClient(Client, message)
    {
        if(message is echoedHeartBeat)
        {
            echoReceived.Enabled= false;
            echoReceived.Reset();
        }        
    }

}

在客户端

class client 
{
     Timer ServerDeadTimeOut = new Timer (1000ms);

     OnStart()
     {
         serverDeadTimeOut.Elapsed += () => { ShowError("Server is dead"); };
         serverDeadTimeOut.isEnabled = true;
     }

     OnNewMessageFromServer(message)
     {
         if(message is HeartBeatMessage)
         {
             serverDeadTimeOut.Reset();
             Send(new HeartBeatEchoMessage);
         }
     }
}

2 个答案:

答案 0 :(得分:3)

我要进行单元测试,集成需要在这两者之间建立某种类型的连接,具体取决于实现方式是更容易或更难。单元测试将仅依赖于抽象。只需将接口注入连接器,并进行单元测试,用Mocks替换它们即可模拟为该类设计的方案。当然,那您必须测试Timer,Logger和ClientListener的实际实现,但这就是单元测试的目的。

public class ConnectorTest
{
    private readonly Connector _connector;
    private readonly TimerMock _receiverMock;
    private readonly TimerMock _senderMock;
    private readonly LoggerMock _loggerMock;
    private readonly ClientListenerMock _listenerMock;

    public void Setup()
    {
        _listenerMock = new ClientListenerMock();
        _receiverMock = new TimerMock();
        _senderMock  = new TimerMock();
        _loggerMock = new LoggerMock();
        _connector = new Connector(_listenerMock, _receiverMock, _senderMock, _loggerMock)
    }

    public void HeartbeatSender_ShouldBeEnabled_OnceClientIsConnected()
    {
        _listenerMock.SimulateClientConnection();

        Assert.IsTrue(_senderMock.IsEnabled);
    }

    public void Error_ShouldBeLogged_WhenConnectedClientDidNotEchoed()
    {
        _listenerMock.SimulateClientConnection();

        _receiverMock.SimulateEchoTimeout();

        Assert.AreEqual("Client did not reply", _loggerMock.RecentErrorMessage);
    }
}

答案 1 :(得分:1)

我认为,如果仅做一些小改动,对此类进行单元测试将非常简单。首先,您一定要摆脱以下问题:

Timer echoReceived = new Timer(200ms);

在每个单元测试中等待200毫秒听起来不是一个好主意。相反,您可以做的是将这200毫秒提取到从构造函数初始化的类字段中,以便可以将其传递给较低的值,例如1毫秒。但是实际上,您可以通过定义类似于ITimer的接口来使其变得更好:

interface ITimer 
{
    int Interval { get; }
    event EventHandler OnElapsed;  
    void Reset();
}

这样,在单元测试中,您可以提供一个存根,例如如此实现

class StubTimer 
{
   [...]
   void TickNow() 
   {
      OnElapsed.Invoke( ... ) // fire the event, to avoid Thread.Sleep (waiting n miliseconds)
   }
   [...]
}

您可能还应该将ShowError函数用作构造函数参数,并将其分配给类型为Action<ErrorArgs>的属性。然后,可以像这样对它进行单元测试:

public void WhenClientConnected_ButThereIsNoResponse_ItShouldCallShowError() 
{
   var stubHearbeatTimer = new StubTimer();
   var stubTimeoutTimer = new StubTimer();

   // i'm pretty sure it's possibile mock Actions with moq as well
   var wasSendErrorCalled = false;
   var stubErrorAction = (args) => {
      wasSendErrorCalled = true;
   };

   var sut = new connector(stubHearbeatTimer, stubTimeoutTimer, stubErrorAction );
   sut.OnClientConnected( ..dummyClient..);
   stubTimeoutTimer.TickNow();

   Assert.IsTrue(wasSendErrorCalled);
}

请注意,这只是一个伪代码。希望这能回答您的问题!