如何测试内部创建和使用 ServerSocket 的方法

How to test method that internally creates and uses a ServerSocket

我的服务器代码如下所示:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server implements Runnable {   
    private ServerSocket serverSocket;
    public Server(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    @Override
    public void run() {
        try {
            Socket client = serverSocket.accept();
            // do stuff
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我的计划是编写一个连接到服务器套接字的模拟客户端,并验证结果,但为了做到这一点,我需要知道要连接到哪个端口。但是,此信息是私人信息。

None 个选项我认为是很好的做法:

  1. 如果我使用预定义的端口号进行测试,我无法保证它一定可用。就算考试前有,理论上也有可能在我尝试使用的时候就抢走了。
  2. 如果我将 0 作为端口号传递(这样 ServerSocket 将自动提供一个空闲端口),我仍然无法访问它。
  3. 我可以向接口添加一个 getServerPort() 方法或创建一个接受 ServerSocket 对象的构造函数,但是仅仅为了测试而更改接口被认为是不好的做法。

正如所写,您的 class 并不真正适合单元测试。

问题是您直接调用 new ServerSocket() 基本上剥夺了您 控制 套接字对象将做什么的能力。

那么,你可以做什么:

interface SocketFactory {
  public ServerSocket createSocketFor(int port);
}

class SocketFactoryImpl implements SocketFactory {
...

public class Server implements Runnable {

  public Server(int port) {
    this(port, new SocketFactoryImpl());
  }

 Server(int port, SocketFactory socketFactory) {
   ...

换句话说:您使用 依赖注入 来为您的 "class under test" 提供一种手段来创建它需要完成其工作的那些对象。

从那里开始:您可以使用 EasyMock 等模拟框架来 控制 mocked SocketFactory 对象 return(可能是一个 mocked ServerSocket 对象)。现在您可以完全控制生产代码使用的 ServerSocket ...您可以测试任何您想要的情况。

长话短说:不要调用 new;而是使用依赖注入来 完全 控制你的 class 被测试。

(也许观看这些 videos 可以真正理解编写可测试代码的意义)。