Модульный тест функциональности сердцебиения - PullRequest
0 голосов
/ 16 ноября 2018

У меня есть клиент-серверная система на основе TCP, закодированная в C #.Меня попросили добавить двунаправленное сердцебиение, чтобы определить, зависли ли какие-либо элементы в сети и не отвечают.

Я решил проблему, отправив сердцебиение с сервера всем подключенным клиентам и дождавшись ответа,Существует тайм-аут, если ответ не приходит вовремя, и в клиентах есть тайм-аут, если сервер не отправил тактовый сигнал в течение 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 голосов
/ 28 ноября 2018

Я думаю, что модульное тестирование этого класса будет довольно простым, если мы просто внесем пару небольших изменений. Прежде всего вы наверняка хотели бы избавиться от следующего:

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

Обратите внимание, что это просто псевдокод. Надеюсь, что это отвечает на ваш вопрос!

0 голосов
/ 23 ноября 2018

Я бы пошел на модульные тесты, для интеграции потребовалось бы установить какую-то связь между этими двумя, в зависимости от реализации это может быть проще или сложнее. Модульные тесты будут просто полагаться на абстракцию. Просто вставьте интерфейсы в Connector и для модульного тестирования замените их на 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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...