我有一个服务器类,故意没有将任何参数传递给它,并且想用Mockito对其进行测试。
如果您想在Github上查看完整的源代码:
Server.class
public class Server extends Thread {
private Other other;
ObjectInputStream fromClient;
ObjectOutputStream toClient;
public Server(){
this.other = new Other(foo, bar);
}
@Override
public void run(){
try{
ServerSocket serverSocket = new ServerSocket(1337);
Socket socket = serverSocket.accept();
fromClient = new ObjectInputStream(socket.getInputStream());
toClient = new ObjectOutputStream(socket.getOutputStream());
while(true) {
int command = (Integer) fromClient.readObject();
switch (command) {
case 0x1:
//add
//...
break;
case 0x2:
//get server data
toClient.writeObject(other.getSomething());
break;
case 0x3:
//delete
//...
break;
default:
break;
}
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread t = new Server();
t.start();
}
}
问题
我知道Mockito不能模拟最终类,例如ObjectOutputStream和ObjectInputStream。
这正是我遇到问题的地方。
到目前为止,我的测试失败,并且在线上出现NullPointerException
when(server.fromClient.readObject()).thenReturn(0x2);
。
当运行最终方法时,这对于Mockito是典型的。
ServerTest.class
@Test
void run() throws IOException, ClassNotFoundException {
//given
Other other = new Other(foo, bar);
Server server = mock(Server.class);
//when
when(server.fromClient.readObject()).thenReturn(0x2);
server.start();
//then
verify(server).toClient.writeObject(other.getSomething());
}
other posts中建议通过实现接口ObjectInput
来更改被测类的签名,从而可以绕过 final 问题,从而进行模拟无论如何。
但是,在提出的方法中,尚不清楚当不将ObjectOutputStream
作为被测类的参数传递时如何操作。
此外,如通过我的ObjectInputStream
结构所见,当ObjectOutputStream
直接控制case
的响应时该如何操作,这对于TCP客户端/服务器应用程序来说并非不常见
到目前为止,我一直认为我的测试会起作用,而不是ObjectOutputStream
签名中的final关键字。如果我错了,请在这里纠正我。
Q:“为什么不将流传递到服务器?” 答:因为我在其他任何地方都不需要它们。
这确实是最后的努力,如果需要的话,我会的,但是我宁愿不这样做。
答案 0 :(得分:2)
您可以在Server
类中提供一个函数,该函数读取fromClient
输入流并模拟它,而不是ObjectInputStream.readObject()
方法:
public class Server extends Thread {
private Other other;
ObjectInputStream fromClient;
ObjectOutputStream toClient;
public Server(){
this.other = new Other(foo, bar);
}
Object readObject() throws ClassNotFoundException, IOException {
return fromClient.readObject();
}
//...
因此您的测试应如下所示:
@Test
void run() throws IOException, ClassNotFoundException {
//given
Other other = new Other(foo, bar);
Server server = mock(Server.class);
//when
when(server.readObject()).thenReturn(0x2);
server.start();
//then
verify(server).toClient.writeObject(other.getSomething());
}
同一件事也可以应用于toClient
流。
答案 1 :(得分:1)
我知道Mockito不能模拟最终类,例如ObjectOutputStream和ObjectInputStream。 > 这正是我遇到问题的地方。
到目前为止,我的测试失败,并且在线上出现NullPointerException
当(server.fromClient.readObject())。thenReturn(0x2);。
当运行最终方法时,这对于Mockito是典型的。
您的NPE不是不是,是因为Mockitos无法模拟final
方法。使用NPE的原因是您的成员变量fromClient
和toClient
在测试代码尝试访问它们时未初始化。
但是,即使这样,它们也将使用实际依赖项实例而不是 mocks 进行初始化,这样Mockito仍然无法对其进行配置。
您从根本上误解了mocks
的目的和技术含义。
您从不模拟待测课程。
您总是要模拟被测类的依赖项,并用这种模拟来代替被测代码的真实依赖项。
在其他文章中建议通过实现接口
final
来更改被测类的签名,从而无论如何对它进行模拟,从而有可能规避ObjectInput
问题。
否。
建议您针对接口进行编程,而不是针对具体类进行编程,并使用依赖注入/控制反转。
没有人建议您在测试中的类(您的Server
类)实现任何接口。
问:“为什么不将流传递到服务器?”
答:因为我在其他任何地方都不需要它们。这确实是最后的努力,如果需要的话,我会的,但是我宁愿不这样做。
我们进行OOP的主要原因之一是代码的可重用性。
单元测试是最明显的代码重用。
测试您的代码有困难,这表明缺乏可重用性。
在这一点上,您的Server
类根本不打算被“重用”并不重要。
这是您的通用编码方法,将来会让您遇到麻烦。
您的Server
类违反了OOP最佳做法:
封装/信息隐藏。
您最有可能声明了成员变量fromClient
和toClient
package private ,以便能够从我们的测试中访问它们。
这有两种问题:
关注点分离
任何班级(提供业务逻辑)都应具有单一职责。
除了业务逻辑之外,实例化依赖项是完全不同的责任。
因此,您的Server
类不应对此实例化负责。
我知道这有点教条,但是它可以导致更好的可测试性和可重用性的代码。 再说一遍:当前的代码是否 根本不打算重用都没关系。
恕我直言,您真正的问题是:如何在不采用OOP最佳实践的情况下测试我的代码?
在这种情况下,您应该使用 PowerMock 明确地将投降提交至不良设计。