使用Microsoft Fakes为SerialPort对象创建Shim的问题
解决VS2017 Fakes中StubSerialPort功能不足的问题
我之前也碰到过这个棘手的问题——在VS2017 Enterprise里给System.dll生成Fakes程序集时,SerialPort类只有Stub没有可用的Shim,而且Stub几乎没什么可用的方法,完全满足不了模拟串口返回预设数据的测试需求。这里给你几个实用的解决方案:
方案一:自定义扩展StubSerialPort
既然默认的StubSerialPort功能有限,我们可以继承它,手动实现需要的方法和属性,把预设数据的逻辑加进去。比如:
public class MockableSerialPort : System.IO.Ports.Fakes.StubSerialPort { private readonly Queue<string> _predefinedData; public MockableSerialPort(Queue<string> predefinedData) { _predefinedData = predefinedData; // 模拟串口打开操作 Open01 = () => { // 可以在这里添加模拟的打开逻辑,比如标记串口状态为已打开 IsOpenGet = () => true; }; // 模拟ReadLine方法,返回预设数据 ReadLine01 = () => { if (_predefinedData.Count > 0) return _predefinedData.Dequeue(); throw new InvalidOperationException("No more mock data to return"); }; // 按需重写其他你需要的方法,比如Write、Close等 Close01 = () => IsOpenGet = () => false; } }
然后在测试用例里这样使用:
[TestClass] public class YourSerialDependentClassTests { [TestMethod] public void ShouldReadPredefinedDataFromSerialPort() { // 准备好要返回的模拟数据队列 var mockDataQueue = new Queue<string>(); mockDataQueue.Enqueue("Sensor Data: 25°C"); mockDataQueue.Enqueue("Sensor Data: 26°C"); // 实例化自定义的Stub var mockPort = new MockableSerialPort(mockDataQueue); // 注入到你要测试的类中(假设你的类通过构造函数依赖SerialPort) var targetClass = new YourSerialDependentClass(mockPort); // 执行测试逻辑并断言结果 var firstReading = targetClass.ReadNextSerialData(); Assert.AreEqual("Sensor Data: 25°C", firstReading); var secondReading = targetClass.ReadNextSerialData(); Assert.AreEqual("Sensor Data: 26°C", secondReading); } }
方案二:抽象串口接口+依赖注入(更推荐)
其实从长远来看,给SerialPort套一层抽象接口,通过依赖注入的方式使用,会让测试变得更灵活,也避开了Fakes的局限性。
首先定义一个包含你业务所需方法的接口:
public interface ISerialPortWrapper { bool IsOpen { get; } void Open(); void Close(); string ReadLine(); void WriteLine(string message); // 其他你需要用到的SerialPort成员 }
然后写一个真实的实现,包装原生的SerialPort:
public class RealSerialPortWrapper : ISerialPortWrapper { private readonly SerialPort _serialPort; public RealSerialPortWrapper(string portName, int baudRate) { _serialPort = new SerialPort(portName, baudRate); } public bool IsOpen => _serialPort.IsOpen; public void Open() => _serialPort.Open(); public void Close() => _serialPort.Close(); public string ReadLine() => _serialPort.ReadLine(); public void WriteLine(string message) => _serialPort.WriteLine(message); }
现在测试的时候,你可以用任何Mock框架(比如Moq)来模拟这个接口,轻松返回预设数据:
[TestMethod] public void TestSerialReadingWithMoq() { // 创建Mock对象 var mockSerialPort = new Mock<ISerialPortWrapper>(); // 设置模拟行为:IsOpen返回true,ReadLine按顺序返回预设数据 mockSerialPort.Setup(p => p.IsOpen).Returns(true); mockSerialPort.SetupSequence(p => p.ReadLine()) .Returns("Mock Response 1") .Returns("Mock Response 2"); // 注入Mock对象到测试类 var testClass = new YourSerialDependentClass(mockSerialPort.Object); // 执行测试并验证结果 Assert.AreEqual("Mock Response 1", testClass.ReadData()); Assert.AreEqual("Mock Response 2", testClass.ReadData()); }
这个方案不仅解决了当前的测试问题,还让你的代码更符合依赖倒置原则,后续维护和扩展也更方便。
方案三:调整Fakes配置尝试生成Shim
有时候默认的Fakes配置可能没有开启SerialPort的Shim生成,你可以手动修改System.fakes配置文件,强制生成Shim:
在你的测试项目的Fakes目录下找到System.fakes文件,添加或修改如下配置:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/"> <Assembly Name="System" Version="4.0.0.0"/> <Namespaces> <Namespace Name="System.IO.Ports" GenerateShims="true" /> </Namespaces> </Fakes>
保存后重新生成Fakes程序集,看看是否会生成ShimSerialPort。不过要注意,VS2017的Fakes对某些类的Shim支持有限,这个方法不一定总能成功,但可以尝试一下。
内容的提问来源于stack exchange,提问作者Mike Lowery
相关产品推荐
相关产品推荐

