【问题标题】:Unit Testing a Client-Server Application in Java用 Java 对客户端-服务器应用程序进行单元测试
【发布时间】:2017-10-02 07:39:35
【问题描述】:

我正在开发一个简单的客户端-服务器应用程序,我应该在不同的线程中初始化我的客户端和服务器对象。服务器包含我大学某些特定部门的所有课程的数据库,GET 请求获取请求部门中的所有课程并将其发送回客户端。虽然我能够在 main 方法中完美地测试所有内容,但单元测试却无法正常工作。下面是我的单元测试代码:

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

当我运行测试时,服务器测试通过,getTest() 测试得到以下输出:

java.net.ConnectException: Connection refused: connect

我一直在四处寻找让单元测试与线程一起工作的最佳方法,但我似乎无法弄清楚。任何帮助将不胜感激。

【问题讨论】:

  • 很难说没有看到这些类的实现 - 但在每次测试后停止服务器可能会有所帮助。
  • serverThread 是局部变量,很可能在事件执行“新服务器(文件)”之前就被释放了
  • 首先:这远非单​​元测试。您正在测试整个系统,而不仅仅是一个代码单元。现在,问题可能是您在启动服务器线程后立即尝试连接到服务器。此时,你无法保证服务器线程正在执行并监听其套接字,因为测试线程和服务器线程同时运行,并且 Thread.start() 不会等待服务器真正启动。跨度>
  • 我遇到了“new Server(file);”的问题,是阻塞调用吗?关闭服务器是什么意思?
  • serverThread 应声明为数据成员,并在每次测试之前/之后 (@After) 开始/停止。

标签: java multithreading sockets junit


【解决方案1】:

首先,正如我的同事所说,这远非单元测试,而是集成或端到端测试。

您的测试的问题可能是某些线程在创建随机失败的测试时优于其他线程,因此您需要一些选项来同步它们。

我可以说使用 JUnit 进行这种测试是绝对可能的,但有一些关键概念需要付诸实践以进行此类测试,涉及多线程、可重现(不是片状)和任何有用的。

如果没有看到 Server 类的实现,这非常困难,但我尝试总结一下希望让您走上正轨的提示。

第一:

在同一个线程中创建您的Server 类和所有其他依赖项(最好在主线程中)。

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
}

重要的是,除了分配像this.someVar = someVar 这样的实例变量之外,构造函数不能做任何“工作”。其背后的概念称为dependency injection,它让您的生活变得更轻松,尤其是在您使用库的情况下。

第二:

您的应用需要钩子,测试可以插入以拦截您的逻辑,您可以在其中使线程停止并等待其他事件发生,这些事件是使您的测试正常工作所必需的。在哪里插入很大程度上取决于您的应用程序。很可能需要为测试中的某些类指定不同的实现,从而以不同的方式连接应用程序的依赖关系图。

要实现这一点,像 CountDownLatch 这样的同步辅助工具是您在测试类中最好的朋友。

@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());
    ...
}

这个例子只是触及了表面上什么是可能的,以及如何利用这个概念来维持高水平的自动化测试,但它应该展示背后的想法。

【讨论】:

    【解决方案2】:

    通常一个 UnitTest 应该只测试一个组件。 “单位”。

    您想要进行某种集成测试。因此,您必须确保 @Test 所需的每个组件都在调用 @Test 之前运行。

    我不确定 JUnit 是否是最好的框架。但是您可以在 @Before 方法中添加一个检查,该方法将使主线程休眠,直到服务器运行。

    例如:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-25
      相关资源
      最近更新 更多