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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:40:16