You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单元测试:如何区分工厂创建的不同端口MockSocket实例?

解决方法:不依赖调用顺序区分不同端口的MockSocket

要在单元测试中准确识别绑定到1234/5678端口的MockSocket,且不依赖CreateTcp()的调用顺序,推荐以下几种实用方案:


方案一:增强MockSocket,跟踪绑定端口

通过给自定义MockSocket添加端口跟踪属性,记录其绑定的端口号,再通过工厂维护的Socket列表筛选目标实例。

实现代码

// 自定义MockSocket,增加端口跟踪
public class MockSocket : ISocket
{
    public int? BoundPort { get; private set; }
    public ISocket AcceptedSocket { get; set; } // 模拟Accept返回的Socket

    public void Bind(EndPoint endPoint)
    {
        if (endPoint is IPEndPoint ipEp)
        {
            BoundPort = ipEp.Port;
        }
    }

    public void Listen(int backlog) {} // 空实现

    public ISocket Accept() => AcceptedSocket; // 返回预设Socket

    public void Send(byte[] data) {} // 可记录数据用于后续验证
}

// Mock工厂,维护已创建的Socket列表
public class MockSocketFactory : ISocketFactory
{
    public List<MockSocket> CreatedSockets { get; } = new();

    public ISocket CreateTcp()
    {
        var socket = new MockSocket();
        CreatedSockets.Add(socket);
        return socket;
    }
}

// 单元测试示例
[Test]
public void Test_1234_Port_Connection()
{
    // Arrange
    var mockFactory = new MockSocketFactory();
    var sut = new ClassToTest(mockFactory);
    
    sut.Listen(IPAddress.Loopback);
    
    // 根据端口找到目标MockSocket
    var socket1234 = mockFactory.CreatedSockets.First(s => s.BoundPort == 1234);
    socket1234.AcceptedSocket = new MockSocket();

    // Act:模拟Accept调用(忽略线程缺陷,直接触发逻辑)
    socket1234.Accept();

    // Assert
    int connectedPort = -1;
    sut.Connected += (_, port) => connectedPort = port;
    Assert.AreEqual(1234, connectedPort);
}

方案二:用Moq框架的回调捕获端口

如果使用Moq等Mock框架,可通过回调函数在Bind调用时捕获端口,关联对应的Mock实例。

实现代码

[Test]
public void Test_1234_Port_With_Moq()
{
    // Arrange
    var mockFactory = new Mock<ISocketFactory>();
    var mockSocket1 = new Mock<ISocket>();
    var mockSocket2 = new Mock<ISocket>();
    
    // 记录每个CreateTcp返回的MockSocket
    int createCount = 0;
    mockFactory.Setup(f => f.CreateTcp())
               .Returns(() => new[] { mockSocket1, mockSocket2 }[createCount++].Object);

    // 绑定端口与MockSocket的映射
    Dictionary<int, Mock<ISocket>> portToSocket = new();
    mockSocket1.Setup(s => s.Bind(It.IsAny<EndPoint>()))
               .Callback<EndPoint>(ep => 
               {
                   if (ep is IPEndPoint ipEp)
                       portToSocket[ipEp.Port] = mockSocket1;
               });
    mockSocket2.Setup(s => s.Bind(It.IsAny<EndPoint>()))
               .Callback<EndPoint>(ep => 
               {
                   if (ep is IPEndPoint ipEp)
                       portToSocket[ipEp.Port] = mockSocket2;
               });

    var sut = new ClassToTest(mockFactory.Object);
    sut.Listen(IPAddress.Loopback);

    // 获取1234端口对应的MockSocket并模拟Accept
    var targetSocket = portToSocket[1234];
    targetSocket.Setup(s => s.Accept()).Returns(new Mock<ISocket>().Object);

    // Act
    targetSocket.Object.Accept();

    // Assert
    int connectedPort = -1;
    sut.Connected += (_, port) => connectedPort = port;
    Assert.AreEqual(1234, connectedPort);
}

方案三:修改工厂接口(可选)

如果允许修改ISocketFactory,可让CreateTcp直接接收端口参数,从根源上消除顺序依赖。

实现代码

// 修改后的工厂接口
public interface ISocketFactory
{
    ISocket CreateTcp(int port);
}

// 被测类的Listen方法调整
public void Listen(IPAddress ipAddress)
{
    var listener1234 = _socketFactory.CreateTcp(1234);
    listener1234.Bind(new IPEndPoint(ipAddress, 1234));
    // 其余逻辑不变
}

// 单元测试示例
[Test]
public void Test_With_Modified_Factory()
{
    var mockFactory = new Mock<ISocketFactory>();
    var mockSocket1234 = new Mock<ISocket>();
    
    // 直接指定端口对应的MockSocket
    mockFactory.Setup(f => f.CreateTcp(1234)).Returns(mockSocket1234.Object);

    var sut = new ClassToTest(mockFactory.Object);
    sut.Listen(IPAddress.Loopback);

    // 直接使用mockSocket1234进行模拟验证
    mockSocket1234.Setup(s => s.Accept()).Returns(new Mock<ISocket>().Object);
    // 后续断言逻辑...
}

推荐方案

优先选择方案一,无需修改被测类的核心逻辑,通过MockSocket的端口跟踪即可实现无顺序依赖的实例区分,兼容性和灵活性最强。

内容的提问来源于stack exchange,提问作者titanicsnake

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 17:55:18