简体   繁体   中英

Unit Testing a Client-Server Application in Java

I'm working on a simple client-server application, and I'm supposed to initialize my client and server objects in separate threads. The server contains a database of all of the courses in some specific departments at my university, and the GET request gets all of the classes in the requested department and sends it back to the client. While I was able to test everything perfectly fine in a main method, unit testing was just not working. Below is my code for the unit test:

import client.Client;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import server.Server;


public class ClientServerTest {
    private static String file = "";

    @BeforeClass
    public static void setFile(){
        file = System.getProperty("file");
    }

    @Before
    public void startServerTest(){
        Thread serverThread = new Thread(() -> {
            new Server(file);
        });
        serverThread.start();
    }

   @Test
   public void getTest(){
        Client client = new Client();
        Assert.assertTrue(client.submitRequest("GET COM"));
        Assert.assertTrue(client.submitRequest("GET MAT"));
        Assert.assertTrue(client.submitRequest("GET BIO"))
    }
}

When I run the test, the server test passes, I get the following output for the getTest() test:

java.net.ConnectException: Connection refused: connect

I've been looking around trying to find the best way to get unit tests to work with threads, but I can't seem to figure it out. Any help would be most appreciated.

Normally a UnitTest should only test a single component. The "Unit".

You whant to do some kind of integration testing. So you have to ensure that every component that you need for your @Test is up an running before @Test is called.

I'm not sure if JUnit is the best framework for this. But you can add a check in the @Before method that will sleep the main thread until the server is running.

For example:

    @Before
    public void startServerTest(){
    Server = new Server();
            Thread serverThread = new Thread(() -> {
                server.init(file);
            });
            serverThread.start();
    while (!server.isRunning() {
        Thread.sleep(1000);
    }
}

First, as my fellows stated, that's far away from a unit test but an integration or end-to-end test.

The problem with your test might be that some threads outperforms other threads creating randomly failing tests so you need some option to synchronize them.

I can say that it's absolutely possible to do this kind of testing with JUnit but there are some key concepts to put into practice to make such tests, involving multiple threads, reproducible (not flaky) and any useful.

Without seeing the implementation of Server class it's pretty difficult but I try to sum up the hints that hopefully put you on the right track.

First:

Create your Server class and all other dependencies inside the same thread (preferably in the main thread).

public static void main(String[] args) {
   Foo otherInstance = new Foo(...);
   Bar anotherInstance = new BarImpl(otherInstance,...);
   Server server = new Server(otherInstance, anotherInstance);
   server.start(); // blocking until app is stopped in a graceful way and where real application logic starts
   // not more to do here
}

Important is that constructors must not do any "work" apart from assigning instance variables like this.someVar = someVar . The concept behind that is called dependency injection and makes your life a lot easier especially if you use a library.

Second:

Your app needs hooks, that tests can plug into to intercept your logic, where you can make threads halt and wait for other events happen that are necessary to make your tests work. Where to plug into depends highly on your application. It's most likely necessary to specify different implementations for some classes inside your tests thus differently wiring the app's dependency graph.

To accomplish that synchronizations aids like CountDownLatch are your best friends inside the test class.

@Test
public void someTest() {
    // or move the instantiation logic to the @Before setup method
    final CountDownLatch isConnected = new CountDownLatch(1);
    Foo otherInstance = new Foo(...);
    Bar anotherInstance = new BarTestImpl(otherInstance,...);
    // here we plug into the connected event
    anotherInstance.setConnectionListener(() -> { isConnected.countDown(); }
    Server server = new Server(otherInstance, anotherInstance);
    server.startAsync(); // not blocking
    ...
    // test logic starting, do some assertions or other stuff
    ...
    // block until connected event is received
    isConnected.await(1, TimeUnit.SECONDS);
    // do some assertions
    assertTrue(server.isConnected());
    ...
}

This example is scratching on the surface of what is possible and how to leverage this concept for maintaining a high level of automated testing but it should show the idea behind.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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