kar*_*bar 7 java sockets multithreading unit-testing mockito
我试图通过从一个线程向另一个线程发送字符串来测试套接字连接,其中服务器和客户端套接字使用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());
}
}
Run Code Online (Sandbox Code Playgroud)
测试运行直到TIMEOUT_TIME然后失败的断言是"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());
}
}
Run Code Online (Sandbox Code Playgroud)
我正在尝试测试的部分内容是run()在内部类中的以下内容connection.listen():
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;
}
}
Run Code Online (Sandbox Code Playgroud)
这是以下部分内容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();
}
...
}
Run Code Online (Sandbox Code Playgroud)
这部分是可以忽略的; 我将尝试逐步完成测试过程,为此问题添加一些额外的上下文.从头开始testConnection():
TcpSocketConnection connection = new TcpSocketConnection(mockServerSocket);
Run Code Online (Sandbox Code Playgroud)
这将与与之关联的模拟ServerSocket创建连接.这将创建一个使用以下行开始的线程:
clientSocket = socket.accept();
Run Code Online (Sandbox Code Playgroud)
由于socket上面是一个引用mockServerSocket,Mockito知道返回对mockTestClientSocket由于这一行调用的模拟Socket的引用:
when(mockServerSocket.accept()).thenReturn(mockTestClientSocket);
Run Code Online (Sandbox Code Playgroud)
接下来是下面的一行:注意:我相信这是我理解和现实不同的地方,因为我相信基于调试这个线程在创建这个OutputStreamWriter对象时会挂起.我还没弄清楚原因.
writer = new OutputStreamWriter(clientSocket.getOutputStream(), DEFAULT_CHARSETNAME);
Run Code Online (Sandbox Code Playgroud)
做一个新的OutputStreamWriter给定OutputStream.Mockito知道模拟客户端套接字的输出流应该是什么样子,因为这setup部分中的这些行:
PipedOutputStream oStream = new PipedOutputStream();
when(mockTestClientSocket.getOutputStream()).thenReturn(oStream);
Run Code Online (Sandbox Code Playgroud)
另外,因为在setup测试之前发生了,所以我们知道我们的InputStream因为这一行而引用了这个OutputStream:
PipedInputStream iStream = new PipedInputStream(oStream);
Run Code Online (Sandbox Code Playgroud)
根据此构造函数的文档,此"创建一个PipedInputStream,以便它连接到管道输出流(oStream).写入(oStream)的数据字节将作为此流的输入提供."
while循环run()开始并导致"Hello,world!" 要发送出OutputStream(也由InputStream接收).接下来,我们很好地包装inputStream:
BufferedReader reader = new BufferedReader(new InputStreamReader(mockTestClientSocket.getInputStream(), DEFAULT_CHARSET));
Run Code Online (Sandbox Code Playgroud)
通过Mockito的魔力,该mockTestClientSocket.getInputStream()调用实际上iStream从之前返回,因为以下行:
when(mockTestClientSocket.getInputStream()).thenReturn(iStream);
Run Code Online (Sandbox Code Playgroud)
所以现在我们有一个带有输入流的阅读器,并且该输入流被连接到输出流.该输出流被挂接到PrintWriter这是println荷兰国际集团"你好,世界!" S.然而,读者似乎永远不会得到ready().
为什么创建的监听器线程在创建期间挂起OutputStreamWriter,我的Hello, World!字符串如何从mocked socket正确发送到mocked客户端?
抱歉成为Mockito/java.net.*newb并且总体上有点厚.我想我已经包含了代码的所有相关部分,但是如果有什么不清楚请告诉我.
我可以通过修改代码中的两件事来使您的单元测试通过:
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());
}
...
Run Code Online (Sandbox Code Playgroud)
当您启动专用线程来管理客户端套接字时,您需要同步线程以确保您的消息可供读取。
为此,您可以:
reader.readLine()但这是一种阻塞方法,因为只要其他线程需要写入该行(此处的消息),当前线程就会等待。代码可以是:
BufferedReader reader = ...
String receivedMessage = reader.readLine();
assertEquals("Hello, world!", receivedMessage);
mockTestClientSocket.close();
connection.closeSocket();
Run Code Online (Sandbox Code Playgroud)
TIMEOUT_TIME足够大甚至夸张的值,以确保另一个线程准备就绪,因此,例如,由于它是一个以纳秒为单位的值,因此您可以将其设置为30秒,以便30_000_000_000L. 如果您没有设置足够大的值,您的测试可能会在缓慢和/或过载和/或共享系统(例如用于持续集成的服务器)中不稳定。