测试Java套接字
我正在开发一个网络应用程序,我想让unit testing正确。 这个时候我们会这样做,你知道吗? 🙂
不过,我在测试网络连接时遇到了麻烦。
在我的应用程序中,我使用普通的java.net.Socket
。
例如:
import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; public class Message { byte[] payload; public Message(byte[] payload) { this.payload = payload; } public boolean sendTo(String hostname, int port) { boolean sent = false; try { Socket socket = new Socket(hostname, port); OutputStream out = socket.getOutputStream(); out.write(payload); socket.close(); sent = true; } catch (UnknownHostException e) { } catch (IOException e) { } return sent; } }
我读到了关于嘲笑但不确定如何应用它。
如果我要测试代码,我会执行以下操作。
首先,重构代码,以便不在要测试的方法中直接实例化Socket
。 下面的例子显示了我能想到的最小变化。 未来的更改可能会将Socket
创建分解为一个完全独立的类,但我喜欢小步骤,我不喜欢对未经测试的代码进行大的更改。
public boolean sendTo(String hostname, int port) { boolean sent = false; try { Socket socket = createSocket(); OutputStream out = socket.getOutputStream(); out.write(payload); socket.close(); sent = true; } catch (UnknownHostException e) { // TODO } catch (IOException e) { // TODO } return sent; } protected Socket createSocket() { return new Socket(); }
既然套接字创建逻辑在您尝试测试的方法之外,您可以开始模拟并挂钩创建套接字。
public class MessageTest { @Test public void testSimplePayload() () { byte[] emptyPayload = new byte[1001]; // Using Mockito final Socket socket = mock(Socket.class); final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); when(socket.getOutputStream()).thenReturn(byteArrayOutputStream); Message text = new Message(emptyPayload) { @Override protected Socket createSocket() { return socket; } }; Assert.assertTrue("Message sent successfully", text.sendTo("localhost", "1234")); Assert.assertEquals("whatever you wanted to send".getBytes(), byteArrayOutputStream.toByteArray()); } }
在要测试的单元上覆盖单个方法对于测试非常有用,尤其是在具有可怕依赖性的丑陋代码中。 显然,最好的解决方案是整理出依赖关系(在这种情况下,我认为Message
不依赖于Socket
,也许有一个作为glowcoder建议的Messager
接口),但是以最小的步骤向解决方案迈进是很好的。
我将按照要求回答您的问题,而不是重新设计您的课程(其他人已经涵盖了这个问题,但是关于课程的基本问题仍然有效)。
unit testing从不测试被测试类之外的任何东西。 这会伤害我的大脑一段时间 – 这意味着unit testing不会以任何方式certificate你的代码有效! 它的作用是certificate您的代码与编写测试时的代码相同。
所以说你想要这个类的unit testing,但你也想要一个function测试。
对于unit testing,您必须能够“模拟”通信。 要做到这一点而不是创建自己的套接字,从“套接字工厂”中获取一个,然后自己做一个套接字工厂。 应该将工厂传入您正在测试的此类的构造函数。 这实际上并不是一个糟糕的设计策略 – 您可以在工厂中设置主机名和端口,这样您就不必在通信类中了解它们 – 更抽象。
现在在测试中你只是传入一个模拟工厂,创建模拟套接字,一切都是玫瑰。
不要忘记function测试! 设置一个可以连接的“测试服务器”,将一些消息发送到服务器并测试您收到的响应。
就此而言,您可能希望进行更深入的function测试,其中您编写一个客户端,向REAL服务器发送一些脚本命令并测试结果。 您甚至可能只想为function测试创建“重置状态”命令。 function测试实际上确保整个“function单元”按预期工作 – 许多unit testing倡导者忘记了这一点。
我不会说这是个坏主意。
我要说它可以改进。
如果您通过套接字发送原始字节[],则另一方可以随意执行任何操作。 现在,如果您没有连接到Java服务器,那么您可能需要这样做。 如果您愿意说“我一直在使用Java服务器”,那么您可以使用序列化来获得优势。
执行此操作时,您可以通过创建自己的Sendable对象来模拟它,就好像它们遇到了线路一样。
创建一个套接字,而不是每个消息一个。
interface Sendable { void callback(Engine engine); }
那么这在实践中如何运作?
信使类:
/** * Class is not thread-safe. Synchronization left as exercise to the reader */ class Messenger { // on the client side Socket socket; ObjectOutputStream out; Messenger(String host, String port) { socket = new Socket(host,port); out = new ObjectOutputStream(socket.getOutputStream()); } void sendMessage(Sendable message) { out.writeObject(out); } }
接收器类:
class Receiver extends Thread { // on the server side Socket socket; ObjectInputStream in; Engine engine; // whatever does your logical data Receiver(Socket socket, Engine engine) { // presumable from new Receiver(serverSocket.accept()); this.socket = socket; this.in = new ObjectInputStream(socket.getInputStream()); } @Override public void run() { while(true) { // we know only Sendables ever come across the wire Sendable message = in.readObject(); message.callback(engine); // message class has behavior for the engine } } }
测试连接和服务器交互很困难。
简而言之,我将业务逻辑与通信逻辑隔离开来。
我为业务逻辑创建unit testing场景,这些测试是自动的(使用JUni,t和Maven),我创建其他场景来测试实际连接,我不使用像JUnit这样的框架来进行这些测试。
在去年我使用Spring Mocks用HttpResponse HttpRequest测试逻辑,但我认为这没用。
我关注这个问题。
再见