@DailyDDose

Тестирование клиент-серверного приложения без Thread.Sleep?

Есть пока что небольшой вебсокет-сервер.
При покрытии этого сервера NUnit-тестами передо мной встал вопрос: а как собственно правильно написать тесты? Ведь приложение то не однопоточное и не синхронное...

Первое что приходит в голову это использование Thread.Sleep:
class ServerTest
{
    string ResponseFromMyServer { get; set; }
    Server MyServer { get; set; }
    WebSocket4Net.WebSocket MyClient { get; set; }
 
    [Test]
    public void Test_Authorization_SuccessAndFailure()
    {
        MyClient.MessageReceived += new EventHandler<WebSocket4Net.MessageReceivedEventArgs>(websocket_MessageReceived);
        Client.SendMessageToServer(message, server);
        for (var i = 0; i < 50 && ResponseFromMyServer == null; ++i)
        {
            System.Threading.Thread.Sleep(100);
        }
        Assert.AreEqual(expected, ResponseFromMyServer);
    }
 
    private void websocket_MessageReceived(object sender, WebSocket4Net.MessageReceivedEventArgs args)
    {
        ResponseFromMyServer = args.Message;
    }
}


Сам по себе подход мне не нравится, да и лишние задержки меня тоже не устраивают.
Каким образом можно тестировать подобные вещи?

Полный код:
using System;
using NUnit.Framework;
 
namespace WebSocketServer.Tests
{
    [TestFixture]
    public class ServerTest
    {
        const string _hostname = "localhost";
        const int _port = 8811;
        const string _protocol = "ws";
 
        string ResponseFromMyServer { get; set; }
        Server MyServer { get; set; }
        WebSocket4Net.WebSocket MyClient { get; set; }
 
        private void websocket_MessageReceived(object sender, WebSocket4Net.MessageReceivedEventArgs args)
        {
            ResponseFromMyServer = args.Message;
        }
 
        [OneTimeSetUp]
        public void RunBeforeAll()
        {
            MyServer = new Server(_hostname, _port, _protocol).Start();
            System.Threading.Thread.Sleep(1000);
 
            MyClient = new WebSocket4Net.WebSocket($"{_protocol}://{_hostname}:{_port}");
            MyClient.MessageReceived += new EventHandler<WebSocket4Net.MessageReceivedEventArgs>(websocket_MessageReceived);
            MyClient.Open();
            System.Threading.Thread.Sleep(3000);
        }
 
        [SetUp]
        public void RunBeforeEach()
        {
            ResponseFromMyServer = null;
        }
 
        [Test]
        public void Test_Authorization_SuccessAndFailure()
        {
            // step 1
            // Тестирование авторизации на успех
            var expected = "{"
                + "\"Type\":\"AUTH_MESSAGE\","
                + "\"Payload\":{\"Message\":null,\"UserName\":\"dailydose\",\"Status\":1}"
                + "}";
            MyClient.Send("{\"Type\": \"AUTH_MESSAGE\", \"Payload\": { \"UserName\": \"dailydose\" }}");
 
            for(var i = 0; i < 50 && ResponseFromMyServer == null; ++i)
            {
                System.Threading.Thread.Sleep(100);
            }
 
            System.Console.WriteLine("expected:" + expected);
            System.Diagnostics.Debug.WriteLine("expected:" + expected);
            System.Console.WriteLine("ResponseFromMyServer:" + ResponseFromMyServer);
            System.Diagnostics.Debug.WriteLine("ResponseFromMyServer:" + ResponseFromMyServer);
            Assert.AreEqual(expected, ResponseFromMyServer);
 
            // step 2
            // Тестирование авторизации другого клиента под уже существующим ником
            ResponseFromMyServer = null;
            var anotherClient = new WebSocket4Net.WebSocket($"{_protocol}://{_hostname}:{_port}");
            anotherClient.MessageReceived += new EventHandler<WebSocket4Net.MessageReceivedEventArgs>(websocket_MessageReceived);
            anotherClient.Open();
            System.Threading.Thread.Sleep(3000);
 
            expected = "{"
                + "\"Type\":\"AUTH_MESSAGE\","
                + "\"Payload\":{\"Message\":\"Error: the user name dailydose is already in use!\",\"UserName\":\"dailydose\",\"Status\":0}"
                + "}";
            anotherClient.Send("{\"Type\": \"AUTH_MESSAGE\", \"Payload\": { \"UserName\": \"dailydose\" }}");
 
            for (var i = 0; i < 50 && ResponseFromMyServer == null; ++i)
            {
                System.Threading.Thread.Sleep(100);
            }
 
            System.Console.WriteLine("expected:" + expected);
            System.Diagnostics.Debug.WriteLine("expected:" + expected);
            System.Console.WriteLine("ResponseFromMyServer:" + ResponseFromMyServer);
            System.Diagnostics.Debug.WriteLine("ResponseFromMyServer:" + ResponseFromMyServer);
            Assert.AreEqual(expected, ResponseFromMyServer);
        }
    }
}
  • Вопрос задан
  • 413 просмотров
Пригласить эксперта
Ответы на вопрос 2
AlexanderYudakov
@AlexanderYudakov
C#, 1С, Android, TypeScript
Вариантов вижу два:

1) переписать движок тестирования так, чтобы он работал асинхронно;

2) использовать Thread.Sleep, добавив немного синтаксического сахара, т.е. вместо:
for (var i = 0; i < 50 && ResponseFromMyServer == null; ++i)
{
    System.Threading.Thread.Sleep(100);
}

использовать:
WaitFor(() => ResponseFromMyServer != null);

Правда, в последнем случае придется добавить вспомогательный код:
spoiler
static void WaitFor(Func<bool> condition)
{
    WaitFor(condition, DefaultWaitTimeout);
}

private static readonly TimeSpan DefaultWaitTimeout = TimeSpan.FromSeconds(20);
        
static void WaitFor(Func<bool> condition, TimeSpan timeout)
{
    var started = DateTime.UtcNow;
    do
    {
        if (condition())
            return;

        Thread.Sleep(50);
    } while (DateTime.UtcNow - started < timeout);
            
    throw new TimeoutException();
}
Ответ написан
@DailyDDose Автор вопроса
Вот немного переписал тест, но вопрос всё еще остаётся открытым.
using System;
using System.Linq;
using NUnit.Framework;
using System.Threading;
 
namespace WebSocketServer.Tests
{
    [TestFixture]
    public class ServerTest
    {
        const string _hostname = "localhost";
        const int _port = 8811;
        const string _protocol = "ws";
 
        string ResponseFromMyServer { get; set; }
        string PreviousResponseFromMyServer { get; set; }
        Server MyServer { get; set; }
        WebSocket4Net.WebSocket MyClient { get; set; }
 
        private void OnReceiveMessageFromServer(object sender, WebSocket4Net.MessageReceivedEventArgs args)
        {
            PreviousResponseFromMyServer = ResponseFromMyServer;
            ResponseFromMyServer = args.Message;
        }
 
        private void Log<T>(T expected)
        {
            Console.WriteLine("expected:" + expected);
            System.Diagnostics.Debug.WriteLine("expected:" + expected);
 
            Console.WriteLine("PreviousResponseFromMyServer:" + PreviousResponseFromMyServer);
            System.Diagnostics.Debug.WriteLine("PreviousResponseFromMyServer:" + PreviousResponseFromMyServer);
 
            Console.WriteLine("ResponseFromMyServer:" + ResponseFromMyServer);
            System.Diagnostics.Debug.WriteLine("ResponseFromMyServer:" + ResponseFromMyServer);
        }
 
        [OneTimeSetUp]
        public void RunBeforeAll()
        {
            MyServer = new Server(_hostname, _port, _protocol).Start();
            Thread.Sleep(1000);
 
            MyClient = new WebSocket4Net.WebSocket($"{_protocol}://{_hostname}:{_port}");
            MyClient.MessageReceived += new EventHandler<WebSocket4Net.MessageReceivedEventArgs>(OnReceiveMessageFromServer);
            MyClient.Open();
            Thread.Sleep(3000);
        }
 
        [SetUp]
        public void RunBeforeEach()
        {
            PreviousResponseFromMyServer = null;
            ResponseFromMyServer = null;
        }
 
        /// <summary>
        /// Тестирование авторизации на успех
        /// </summary>
        [Test, Order(1)]
        public void Test_Authorization_Success()
        {
            var expected = MyServer.BuildMessage(
                AuthMessage.Type,
                new AuthMessage()
                {
                    UserName = "DailyDose",
                    Status = AuthMessage.StatusCode.SUCCESS
                }
            );
            MyClient.Send(
                MyServer.BuildMessage(
                    AuthMessage.Type,
                    new AuthMessage()
                    {
                        UserName = "DailyDose"
                    }
                )
            );
 
            for (var i = 0; i < 50 && ResponseFromMyServer == null; ++i)
            {
                Thread.Sleep(100);
            }
 
            Log(expected);
            var possibleValues = new[] { PreviousResponseFromMyServer, ResponseFromMyServer };
            Assert.IsTrue(possibleValues.Contains(expected));
        }
 
        /// <summary>
        /// Тестирование авторизации другого клиента под уже существующим ником
        /// </summary>
        [Test, Order(2)]
        public void Test_Authorization_Error()
        {
            var anotherClient = new WebSocket4Net.WebSocket($"{_protocol}://{_hostname}:{_port}");
            anotherClient.MessageReceived += new EventHandler<WebSocket4Net.MessageReceivedEventArgs>(OnReceiveMessageFromServer);
            anotherClient.Open();
            Thread.Sleep(3000);
 
            var expected = MyServer.BuildMessage(
                AuthMessage.Type,
                new AuthMessage()
                {
                    Message = "Error: the user name <DailyDose> is already in use!",
                    UserName = "DailyDose",
                    Status = AuthMessage.StatusCode.ERROR
                }
            );
            anotherClient.Send(
                MyServer.BuildMessage(
                    AuthMessage.Type,
                    new AuthMessage()
                    {
                        UserName = "DailyDose"
                    }
                )
            );
 
            for (var i = 0; i < 50 && ResponseFromMyServer == null; ++i)
            {
                Thread.Sleep(100);
            }
 
            Log(expected);
            Assert.AreEqual(expected, ResponseFromMyServer);
        }
    }
}
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы