简体   繁体   English

单元测试心跳功能

[英]Unit Test heartbeat functionality

I have a client-server system based on TCP coded in C#. 我有一个基于C#编码的TCP的客户端服务器系统。 I was requested to add a bidirectional heartbeat to discover if any of the elements in the network freeze and are not responding. 我被要求添加双向心跳,以发现网络中的任何元素是否冻结并且没有响应。

I solved the problem by sending a heartbeat from the server to all connected clients and waiting for the response. 我通过从服务器向所有连接的客户端发送心跳并等待响应来解决了该问题。 There's a timeout if the response doesn't come on time and there is a timeout in the clients if the server didn't send any heartbeat in 2 seconds (assuming the server is frozen). 如果响应没有准时出现,则超时,如果服务器在2秒钟内未发送任何心跳(假设服务器已冻结),则客户端中将存在超时。

Everything happens internally and the server nor the client expose any of the events (receiving a heartbeat, replying a heartbeat happen silently, as it was requested) 一切都在内部发生,并且服务器或客户端都不会公开任何事件(接收心跳,回复心跳会按要求静默发生)

The thing is .. How does one create a unit test or integration test for such a functionality? 问题是..如何为这种功能创建单元测试或集成测试?

Note: I'm coding in visual studio, C#, .Net 4.6.1, testing using NUnit3 注意:我正在Visual Studio,C#、. Net 4.6.1中进行编码,使用NUnit3进行测试

Brief pseudocode example: 简短的伪代码示例:

//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();
        }        
    }

}

On the client side 在客户端

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);
         }
     }
}

I'd go for Unit Tests, integration would require setting up some kind of connection between these two, depending on implementation it can be easier or harder. 我要进行单元测试,集成需要在这两者之间建立某种连接,具体取决于实现的难易程度。 Unit Tests would simply rely on the abstraction. 单元测试将仅依赖于抽象。 Just inject interfaces to the Connector and for unit testing replace them with Mocks to simulate scenarios that you designed that class for. 只需将接口注入连接器,并进行单元测试,用Mocks替换它们即可模拟为该类设计的方案。 Of course then you have to test actual implementations of Timer, Logger and ClientListener, but that's what unit tests are for. 当然,然后您必须测试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);
    }
}

I think unit testing this class would be fairly simple if we just make a couple of small changes. 我认为,如果仅做一些小改动,对此类的单元测试将相当简单。 First of all you'd surely like to get rid of the following: 首先,您一定要摆脱以下问题:

Timer echoReceived = new Timer(200ms);

Waiting 200ms in a each unit test doesn't sound like a good idea. 在每个单元测试中等待200ms听起来不是一个好主意。 What you could do instead, is to extract this 200 ms into a class field initialized from the constructor so that you can pass it lower values, such as 1ms. 相反,您可以做的是将这200毫秒提取到从构造函数初始化的类字段中,以便可以将其传递给较低的值,例如1毫秒。 But actually, you could make it even better by defining an interface like ITimer that could look something like: 但是实际上,您可以通过定义类似于ITimer的接口来使它变得更好:

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

This way, in the unit test you could provide a stub eg implemented like this 这样,在单元测试中,您可以提供一个存根,例如这样实现

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

You probabbly should also take ShowError function as constructor parameter, assigning it to property of type like Action<ErrorArgs> .Then, you can just unit test it like so: 您可能还应该将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);
}

Please note it's just a pseudecode though. 请注意,这只是一个伪代码。 Hope this answers your question! 希望这能回答您的问题!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM