D
D
DailyDDose2018-01-02 14:42:07
C++ / C#
DailyDDose, 2018-01-02 14:42:07

Testing a client-server application without Thread.Sleep?

There is so far a small web socket server.
When covering this server with NUnit tests, the question arose before me: how to actually write the tests correctly? After all, the application is not single-threaded and not synchronous ...
The first thing that comes to mind is the use of 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;
    }
}

By itself, I do not like the approach, and the extra delays do not suit me either.
How can you test things like this?
Full code:
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);
        }
    }
}

Answer the question

In order to leave comments, you need to log in

2 answer(s)
A
Alexander Yudakov, 2018-01-02
@AlexanderYudakov

I see two options:
1) rewrite the testing engine so that it works asynchronously;
2) use Thread.Sleep by adding some syntactic sugar i.e. instead of:

for (var i = 0; i < 50 && ResponseFromMyServer == null; ++i)
{
    System.Threading.Thread.Sleep(100);
}

use: True, in the latter case, you will have to add auxiliary code:
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();
}

D
DailyDDose, 2018-01-02
@DailyDDose

I rewrote the test a bit, but the question is still open.

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

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question