2011-04-07 51 views
26

我正在开发一个网络应用程序,我希望得到单元测试的权利。这次我们会做,你知道吗? :)测试Java套接字

虽然我有麻烦测试网络连接。

在我的应用程序中,我使用普通的java.net.Socket s。

例如:

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; 
    } 
} 

我读到嘲笑,但我不知道如何使用它。

+3

而不是嘲笑一个套接字它更简单恕我直言,以创建一个实际的ServerSocket(需要两行) – 2011-04-07 09:54:00

回答

47

如果我是测试代码,我会做以下。

首先,重构代码,以便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接口),但在尽可能最小的步骤中迈向解决方案是很好的。

+1

非常感谢,这正是我正在寻找的 – 2011-04-13 09:18:07

+1

优秀的方法!谢谢。 – Serge 2013-07-12 09:50:16

+0

我在执行'mock(Socket.class)'时遇到'java.lang.VerifyError'异常,你碰巧知道为什么吗? – 2016-04-12 10:00:27

-1

这很难测试连接和服务器的交互。

最后,我将业务逻辑从通信逻辑中分离出来。我创建了业务逻辑的单元测试场景,这些测试是自动的(使用JUni,t和Maven),并且我创建了其他场景来测试真实连接,我没有像这些测试那样使用像JUnit这样的框架。

在去年我使用Spring Mocks来测试HttpResponse HttpRequest的逻辑,但我认为这没有用。

我遵循这个问题。

再见

+2

我也尝试分离业务和通信逻辑,但也想测试我的通信代码。我发现杰夫福斯特的解决方案非常有帮助。 – 2011-04-13 11:52:14

2

我不会说这是一个坏主意。

我想说它可以改进。

如果您通过您的套接字通过原始字节[]发送,则另一方可以随心所欲地执行任何操作。现在,如果你没有连接到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 
     } 
    } 
} 
+0

感谢您的回答,但实际上我会与非java服务器和客户端进行交互,因此序列化不适合我。 – 2011-04-13 12:00:27

+2

尽管您可能试图测试现有代码,但像我这样的其他人可能正在为新项目进行研究。对于那个人 - 有平台独立的序列化。查看https://github.com/google/protobuf/ – kd8azz 2014-12-24 03:54:56

+0

或简单的JSON。 – Piovezan 2017-07-28 18:41:44

10

我将按照问题回答你的问题,而不是重新设计你的班级(其他人有这样的报道,但是关于班级的基本问题是有效的)。

单元测试从不测试被测试类以外的任何东西。这伤害了我的大脑一段时间 - 这意味着单元测试并没有以任何方式证明您的代码工作!它所做的就是证明你的代码与你编写测试时的相同。

所以说你想要这个类的单元测试,但你也想要一个功能测试。

对于单元测试,您必须能够“模拟”通信。要做到这一点,而不是创建自己的套接字,从“套接字工厂”取一个,然后让自己成为套接字工厂。工厂应该被传递给你正在测试的这个类的构造函数。这实际上并不是一个糟糕的设计策略 - 您可以在工厂中设置主机名和端口,这样您就不必在通信类中了解它们 - 更抽象。

现在在测试中,您只需传入一个模拟工厂,创建模拟套接字,一切都是玫瑰。

不要忘了功能测试!建立一个你可以连接的“测试服务器”,发送一些消息到服务器并测试你回复的响应。

对于这个问题,你可能想要做更深入的功能测试,你写一个客户端发送REAL服务器的一些脚本命令并测试结果。你可能甚至想创建一个“复位状态”命令来进行功能测试。功能测试实际上确保整个“功能单元”按照您的期望一起工作 - 这是许多单元测试提倡者所忘记的。