我们如何编写 Socket 连接的测试用例

How can we write test cases for Socket connections

下面是需要测试Socket连接真假场景的代码片段。

public boolean pingHost(String hostname, int port) {
    try (Socket socket = new Socket()) {
        socket.connect(new InetSocketAddress(hostname, port), 3000);
        return true;
    } catch (IOException e) {
        return false;
    }
}

这是模拟的标准用例。使用 PowerMock,即您可以模拟构造函数调用和 return 模拟对象。您还可以在构造函数调用中验证正确的参数。只需搜索 'Powermock mocking constructor'。 如果模拟不是您的选择,另一种方法是使用可用于 *NIX 和 Windows 平台的 netcat 在端口上侦听。

编辑 2021.12.16 ----------------

您需要包含这四个 jar 才能使用 Mockito 和 PowerMock (Maven pom)

    <!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>4.1.0</version>
        <scope>test</scope>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-inline</artifactId>
        <version>4.1.0</version>
        <scope>test</scope>
    </dependency>

    <!-- https://www.baeldung.com/intro-to-powermock -->
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>2.0.9</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito2</artifactId>
        <version>2.0.9</version>
        <scope>test</scope>
    </dependency>

但是有很多关于如何设置 Mockito 和 PowerMock 的文档。

这是一个带有一些注释的可运行示例,让您大致了解如何使用模拟进行测试。

package __scratchpad__;

import static org.junit.Assert.fail;
import static org.powermock.api.mockito.PowerMockito.whenNew;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;



@RunWith(PowerMockRunner.class)
@PrepareForTest(PingerTest.Pinger.class)
public class PingerTest {

    
    // Inner class just to have a compact 'all in one' example
    public class Pinger {
        
        // refactored to better coding style (avoid 'Magic numbers') 
        // AND for testability because TIM-OUT is now accessible for test
        public static final int TIME_OUT = 3000;

        public boolean pingHost(String hostname, int port) {
            
            try (Socket socket = new Socket()){
                socket.connect(new InetSocketAddress(hostname, port), TIME_OUT);
                return true;
            } catch (IOException e) {
                return false;
            }
        }
        
    }
    

    @Test
    public void testPingHost() {
        
        // create required mock objects to inject 
        InetSocketAddress addressMock = Mockito.mock(InetSocketAddress.class);
        Socket socketMock = Mockito.mock(Socket.class);
        
        // create parameters 
        final String hostname = "answer.deepthought";
        final int port = 42;
        final int timeOut = PingerTest.Pinger.TIME_OUT;
        
        // create class under test
        Pinger pingerUnderTest = new Pinger();
        
        try {
            
            // mock constructor call of class InetSocketAddress and verify correct parameters
            // If parameters of call are different to the expected, than NO mock object is returned, the test will fail
            // Otherwise the addressMock object is returned
            whenNew(InetSocketAddress.class)
              .withArguments(hostname, port)
              .thenReturn(addressMock);
            
            // mock constructor call of Socket class
            whenNew(Socket.class)
              .withNoArguments()
              .thenReturn(socketMock);
            
            // call method under test with parameters defined earlier.
            // If parameters are different to them of InetSocketAddress constructor call the test will fail.
            pingerUnderTest.pingHost(hostname, port);
            
            // if all works fine exactly one call to method connect() with specified parameters MUST appear
            // if there are more or less calls or any of the parameters is different, than the test will fail
            Mockito.verify(socketMock).connect(addressMock, timeOut);
            
        } catch(Exception e) {
            e.printStackTrace();
            fail("Unexpected exception caught!");
        }
        
    }

}