Domanda

I built an async multi-client TCP server for RPC usage. It's working well but I've found it difficult to unit test certain functionality:

  1. Connect 2x clients, is client count 2
  2. Connect 1x client, disconnect client, is client count zero

I want to test that the server is robust with handling disconnects and multiple connections. The below test fails only due to scheduling.

Unit Test

        [TestMethod]
        public void Start_TwoConnections_ClientsIsTwo()
        {
            var handler = new HandlerStub();
            using (server = new APIServer(handler))
            using (var client1 = new TcpClient())
            using (var client2 = new TcpClient())
            {
                server.Start();
                client1.Connect(IPAddress.Loopback, port);
                client2.Connect(IPAddress.Loopback, port);
                // await Task.Delay(500); <-- This will fix the problem, but is surely unreliable.
                Assert.AreEqual(2, server.Clients);
            }
        }

Server Snippet

        public void Start()
        {
            // Root try-catch, for unexpected errors
            try
            {
                server = new TcpListener(IPAddress.Loopback, 8352);
                IsRunning = true;
                do // Retry loop
                {
                    // Start server errors
                    try
                    {
                        server.Start();
                        var task = Task.Run(AcceptConnections);
                    }
                    catch (SocketException ex)
                    {
                        Console.WriteLine(string.Format("Error {0}: Failed to start server.", ex.ErrorCode));
                    }
                }
                while (!server.Server.IsBound && !IsDisposed);
            }
            catch (Exception ex)
            {
                IsRunning = false;
                Console.WriteLine(string.Format("Unexpected Error: {0}", ex.ToString()));
                throw ex;
            }
        }

        private async Task AcceptConnections()
        {
            try
            {            
                // Multi-client listener loop
                do
                {
                    var connection = await AcceptConnection();
                    connections.Add(connection);
                }
                while (!IsDisposed && IsRunning);
            }
            catch (SocketException ex)
            {
                Console.WriteLine(string.Format("Error {0}: Server socket error.", ex.ErrorCode));
                CleanupConnections();
            }
        }

How can this code be refactored to improve it's test-ability?

È stato utile?

Soluzione

By exposing TcpListener.Pending() on my APIServer, I was able to resolve this.

APIServer.cs

        TcpListener server;
        public bool Pending
        {
            get
            {
                return server.Pending();
            }
        }

Unit Test

        [TestMethod]
        public async Task Start_TwoConnections_ClientsIsTwo()
        {
            var gazeStub = new GazeServerStub();
            using (server = new APIServer(gazeStub))
            using (var client1 = new TcpClient())
            using (var client2 = new TcpClient())
            {
                server.Start();
                client1.Connect(IPAddress.Loopback, port);
                client2.Connect(IPAddress.Loopback, port);
                await WaitForNoPendingConnections(server);
                Assert.AreEqual(2, server.Clients);
            }
        }

        private async Task WaitForNoPendingConnections(APIServer server)
        {
            while (server.Pending)
            {
                await Task.Delay(50);
            }
        }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
scroll top