2011-04-07 52 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服務器的一些腳本命令並測試結果。你可能甚至想創建一個「復位狀態」命令來進行功能測試。功能測試實際上確保整個「功能單元」按照您的期望一起工作 - 這是許多單元測試提倡者所忘記的。