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

无法修改外部Item类时,如何测试Data类DoWork是否调用Update方法?

解决方案

要验证Data.DoWork()是否正确调用了Item1.Update("Q")和Item2.Update("W"),同时避免耦合Item类的内部实现,分两种场景给出方案:

场景1:允许修改Data类(推荐,符合依赖倒置原则)

这种方式通过抽象解耦,是长期维护的最优解:

  1. 定义抽象接口:
    创建一个包含Update方法的接口,统一Item1和Item2的行为:

    public interface IUpdatable
    {
        void Update(string value);
    }
    
  2. 实现适配器类:
    为外部的Item1和Item2编写适配器,实现IUpdatable接口,转发调用到原类:

    public class Item1Adapter : IUpdatable
    {
        private readonly Item1 _item;
        public Item1Adapter(Item1 item) => _item = item;
        public void Update(string value) => _item.Update(value);
    }
    
    public class Item2Adapter : IUpdatable
    {
        private readonly Item2 _item;
        public Item2Adapter(Item2 item) => _item = item;
        public void Update(string value) => _item.Update(value);
    }
    
  3. 修改Data类依赖抽象:
    将Data类中的具体类型替换为接口,支持依赖注入:

    public class Data
    {
        public IUpdatable Item1 { get; set; }
        public IUpdatable Item2 { get; set; }
    
        public void Dowork()
        {
            Item1.Update("Q");
            Item2.Update("W");
        }
    }
    
  4. 编写测试用例:
    使用Moq等开源模拟框架,验证接口方法的调用情况:

    [TestClass]
    public class DataTests
    {
        [TestMethod]
        public void DoWork_CallsUpdateWithCorrectParameters()
        {
            // 准备模拟对象
            var mockItem1 = new Mock<IUpdatable>();
            var mockItem2 = new Mock<IUpdatable>();
            var data = new Data { Item1 = mockItem1.Object, Item2 = mockItem2.Object };
    
            // 执行方法
            data.Dowork();
    
            // 验证调用:检查Update是否被调用一次,参数为"Q"和"W"
            mockItem1.Verify(x => x.Update("Q"), Times.Once);
            mockItem2.Verify(x => x.Update("W"), Times.Once);
        }
    }
    

场景2:无法修改Data类(完全依赖外部类)

如果不能调整Data的依赖结构,可通过以下方式实现无耦合测试:

方法A:使用支持非虚方法模拟的商业框架

像TypeMock Isolator或Telerik JustMock这类框架可以直接拦截非虚方法调用,无需修改原代码:

[Test]
public void DoWork_VerifiesUpdateCalls()
{
    // 创建Item1和Item2的模拟实例
    var mockItem1 = Mock.Create<Item1>();
    var mockItem2 = Mock.Create<Item2>();
    var data = new Data { Item1 = mockItem1, Item2 = mockItem2 };

    // 执行测试方法
    data.Dowork();

    // 验证Update方法的调用次数和参数
    Mock.Assert(() => mockItem1.Update("Q"), Occurs.Once());
    Mock.Assert(() => mockItem2.Update("W"), Occurs.Once());
}

方法B:用Castle DynamicProxy创建动态代理(开源方案)

利用Castle.Core的动态代理功能,拦截非虚方法调用并记录状态:

  1. 安装NuGet包Castle.Core

  2. 编写拦截器类,记录调用情况:

    public class UpdateCallTracker : IInterceptor
    {
        public bool UpdateCalled { get; private set; }
        public string LastUpdateValue { get; private set; }
    
        public void Intercept(IInvocation invocation)
        {
            if (invocation.Method.Name == nameof(Item1.Update))
            {
                UpdateCalled = true;
                LastUpdateValue = invocation.Arguments[0] as string;
            }
            // 可选:调用原方法,若不需要验证Item内部逻辑可跳过
            invocation.Proceed();
        }
    }
    
  3. 编写测试用例:

    [TestMethod]
    public void DoWork_RecordsUpdateCalls()
    {
        // 创建代理生成器和拦截器
        var proxyGenerator = new ProxyGenerator();
        var tracker1 = new UpdateCallTracker();
        var tracker2 = new UpdateCallTracker();
    
        // 生成Item1和Item2的代理实例
        var item1Proxy = proxyGenerator.CreateClassProxy<Item1>(tracker1);
        var item2Proxy = proxyGenerator.CreateClassProxy<Item2>(tracker2);
    
        var data = new Data { Item1 = item1Proxy, Item2 = item2Proxy };
    
        // 执行方法
        data.Dowork();
    
        // 验证调用状态
        Assert.IsTrue(tracker1.UpdateCalled);
        Assert.AreEqual("Q", tracker1.LastUpdateValue);
        Assert.IsTrue(tracker2.UpdateCalled);
        Assert.AreEqual("W", tracker2.LastUpdateValue);
    }
    

注意事项

  • 如果Item1或Item2是密封类(sealed),动态代理方案会失效,此时只能选择商业框架或更底层的方法注入(如Fody织入)。
  • 优先选择场景1的抽象解耦方案,这能让代码更易维护,测试更稳定。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 01:15:52