使用Mockito模拟服务器-客户端连接

u0sqgete  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(145)

简介

我试图通过从一个线程向另一个线程发送字符串来测试套接字连接,其中服务器和客户端套接字是用Mockito v1.9.5模拟的。
下面是我正在尝试运行的测试:

@Test
public void testConnection() {        
    //associate a mock server socket with the TCP Connection
    TcpSocketConnection connection = new TcpSocketConnection(mockServerSocket);
    try {
        //begin thread which listens for clients, then sends "Hello, world" to a connected
        //client.
        connection.listen();
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(mockTestClientSocket.getInputStream(), DEFAULT_CHARSET)
        );

        long startTime = System.nanoTime();
        for (long interval = 0;
                interval < TIMEOUT_TIME;
                interval = System.nanoTime() - startTime) {
            if (reader.ready()) {
                String receivedMessage = reader.readLine();
                assertEquals("Hello, world!", receivedMessage);
                mockTestClientSocket.close();
                connection.closeSocket();
                return;
            }
        }
        mockTestClientSocket.close();
        connection.closeSocket();
        fail("Failed to receive message.");
    } catch (IOException e) {
        fail(e.getMessage());
    }
}

字符串
测试一直运行到TIMEOUT_TIME,然后失败的Assert是"Failed to receive message.",我在这里指定了模拟对象的行为:

@Before
public void setup() {
    mockServerSocket = mock(ServerSocket.class);
    try {
        when(mockServerSocket.accept()).thenReturn(mockTestClientSocket);
    } catch (IOException e) {
        fail(e.getMessage());
    }

    mockTestClientSocket = mock(Socket.class);
    try {
        PipedOutputStream oStream = new PipedOutputStream();
        when(mockTestClientSocket.getOutputStream()).thenReturn(oStream);

        PipedInputStream iStream = new PipedInputStream(oStream);
        when(mockTestClientSocket.getInputStream()).thenReturn(iStream);

        when(mockTestClientSocket.isClosed()).thenReturn(false);
    } catch (IOException e) {
        fail(e.getMessage());
    }
}


我试图测试的部分内容是在connection.listen()中启动的内部类中的以下run()

class InnerListenerClass implements Runnable {
    @Override
    public void run() {
        try {
            clientSocket = socket.accept();
            writer = new OutputStreamWriter(
               clientSocket.getOutputStream(), DEFAULT_CHARSETNAME);
            out = new PrintWriter(writer, true);
            while (!clientSocket.isClosed()) {
                out.println("Hello, world!");
                Thread.sleep(MILLIS_BETWEEN_MESSAGES);
            }
        } catch (InterruptedException | IOException e) {
            LOG.debug(e.getMessage());
        }
    }

    public InnerListenerClass(final ServerSocket socket) {
        this.socket = socket;
    } 
}


下面是TcpSocketConnection.java的一部分:

class TcpSocketConnection() {
    public TcpSocketConnection(final ServerSocket serverSocket) {
        checkNotNull(serverSocket);
        this.serverSocket = serverSocket;
    }
    ...
    public final void listen() throws IOException {
        listenerThread = new Thread(new InnerListenerClass(serverSocket));
        listenerThread.start();
    }
    ...
}

它是如何在我的脑海中运作的

我将尝试逐步完成我的测试过程,为这个问题添加一些额外的上下文。从testConnection()的开头开始:

TcpSocketConnection connection = new TcpSocketConnection(mockServerSocket);


这将创建一个连接,并将一个模拟的ServerSocket与之关联。这将创建一个线程,该线程以下面的行开始:

clientSocket = socket.accept();


由于上面的socket是对mockServerSocket的引用,因此Mockito知道返回对名为mockTestClientSocket的mock Socket的引用,因为这行:

when(mockServerSocket.accept()).thenReturn(mockTestClientSocket);


下一个是下面的一行:* 注:我相信这就是我的理解和现实的分歧,因为我相信基于调试,这个线程挂在创建这个OutputStreamWriter对象上。我还没有弄清楚为什么。

writer = new OutputStreamWriter(clientSocket.getOutputStream(), DEFAULT_CHARSETNAME);


在给定OutputStream的情况下,创建一个新的OutputStreamWriter。Mockito知道模拟的客户端套接字的输出流应该是什么样子,因为setup部分中有以下几行:

PipedOutputStream oStream = new PipedOutputStream();                 
when(mockTestClientSocket.getOutputStream()).thenReturn(oStream);


另外,因为setup发生在测试之前,我们知道我们的InputStream有一个对这个OutputStream的引用,因为这行:

PipedInputStream iStream = new PipedInputStream(oStream);


根据此构造函数的文档,This “创建一个PipedInputStream,使其连接到管道输出流(oStream)。写入(oStream)的数据字节将可用作此流的输入。"
run()中的while循环开始,并导致“Hello,world!”被发送到OutputStream(也被InputStream接收)。接下来,我们很好地 Package 了inputStream:

BufferedReader reader = new BufferedReader(new InputStreamReader(mockTestClientSocket.getInputStream(), DEFAULT_CHARSET));


通过Mockito的魔力,mockTestClientSocket.getInputStream()调用实际上返回了之前的iStream,因为下面这行代码:

when(mockTestClientSocket.getInputStream()).thenReturn(iStream);


所以现在我们有一个读取器,它有一个输入流,输入流连接到一个输出流。输出流连接到一个PrintWriter,也就是println ing“Hello,world!“s。然而,读取器似乎从来没有得到ready()

问题

  • 为什么在创建OutputStreamWriter的过程中,创建的侦听器线程挂起,以及如何将Hello, World!字符串从模拟套接字正确发送到模拟客户端?*
x7yiwoj4

x7yiwoj41#

我可以通过修改代码中的两件事来让你的单元测试通过:

1.正确模拟mockServerSocket.accept()

到目前为止,你模仿mockServerSocket.accept()太早了,因为mockTestClientSocket还没有被设置,所以它将返回null,你需要先设置它,所以你的代码应该是:

mockServerSocket = mock(ServerSocket.class);
// Set it first
mockTestClientSocket = mock(Socket.class);

try {
    // Then mock it
    when(mockServerSocket.accept()).thenReturn(mockTestClientSocket);
} catch (IOException e) {
    fail(e.getMessage());
}
...

字符串

2.将线程设置为正确

当你启动一个专用线程来管理你的客户端套接字时,你需要同步你的线程,以确保你的消息可以被读取。

要做到这一点,您可以:

1.通过调用reader.readLine()来重写代码,但这是一种阻塞的方法,因为当前线程将等待其他线程写入该行(此处的消息)。

  • 代码可能是:*
BufferedReader reader = ...

String receivedMessage = reader.readLine();
assertEquals("Hello, world!", receivedMessage);
mockTestClientSocket.close();
connection.closeSocket();


1.为TIMEOUT_TIME设置一个足够大的值,甚至是夸张的大,以确保其他线程将准备就绪,例如,因为它是一个以纳秒为单位的值,您可以将其设置为30秒,因此可以设置为30_000_000_000L。如果您没有设置足够大的值,您的测试可能会在缓慢和/或过载和/或共享系统中不稳定,例如用于持续集成的服务器。

相关问题