单元测试:如何区分工厂创建的不同端口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
相关产品推荐
相关产品推荐

