基于NUnit的非虚基方法Mock及多子类测试优化方案咨询
一、初始场景:非虚基方法无法Mock的问题
首先,Moq这类主流Mock框架是基于多态实现的,只能Mock虚方法、抽象方法或者接口方法,非虚方法因为无法被重写,所以直接用Setup(x=>x.BaseFunctionB())肯定会失败。给你两个可行的方向:
1. 商业隔离框架(快速解决,但有成本)
像TypeMock Isolator或者Telerik JustMock这类工具可以绕过.NET的访问限制,直接Mock非虚方法、静态方法甚至密封类。如果项目预算允许,这是最省事的方案,不用改现有代码。
2. 包装层重构(无成本,更符合设计原则)
如果不想用商业工具,建议给BaseClass的非虚方法做一个可Mock的包装层,通过依赖注入解耦:
// 定义包装接口 public interface IBaseClassWrapper { string BaseFunctionB(); } // 生产环境的真实实现 public class BaseClassWrapper : IBaseClassWrapper { private readonly BaseClass _baseInstance; public BaseClassWrapper(BaseClass baseInstance) => _baseInstance = baseInstance; public string BaseFunctionB() => _baseInstance.BaseFunctionB(); } // 修改MyClass,注入包装依赖 class MyClass : BaseClass { private readonly IBaseClassWrapper _wrapper; // 构造函数注入,测试传Mock,生产传真实Wrapper public MyClass(IBaseClassWrapper wrapper) => _wrapper = wrapper; public override void FunctionA() { var a = _wrapper.BaseFunctionB(); // 替换原直接调用 // 原有逻辑不变 } }
测试时就可以轻松MockIBaseClassWrapper:
var mockWrapper = new Mock<IBaseClassWrapper>(); mockWrapper.Setup(x => x.BaseFunctionB()).Returns("my values"); var myClass = new MyClass(mockWrapper.Object); // 测试FunctionA逻辑
二、更新场景:批量测试子类与代码覆盖率问题
你现在用继承子类重写GetValue的方式太繁琐,而且Mock代理导致覆盖率统计失效,推荐用**Moq Partial Mock(部分Mock)**来解决:
核心思路:
BaseClass2的GetValue是virtual(虽然用new重写了基类方法,但本身是虚的),所以可以直接创建MyClassN的部分Mock,只覆盖GetValue的行为,其他方法(比如你要测试的GenerateOutPut)走真实逻辑,这样既不用写一堆TestClass,也能正常统计代码覆盖率。
示例代码:
[Test] public void TestMyClass1_GenerateOutput() { // 创建MyClass1的部分Mock,CallBase=true表示未Setup的方法走真实实现 var myClassMock = new Mock<MyClass1>() { CallBase = true }; // 只Mock GetValue方法,返回测试需要的值 myClassMock.Setup(x => x.GetValue()).Returns(100); // 调用真实的GenerateOutPut方法 var result = myClassMock.Object.GenerateOutPut(); Assert.AreEqual(101, result); } [Test] public void TestMyClass2_GenerateOutput() { var myClassMock = new Mock<MyClass2>() { CallBase = true }; myClassMock.Setup(x => x.GetValue()).Returns(100); var result = myClassMock.Object.GenerateOutPut(); Assert.AreEqual(99, result); }
为什么能解决覆盖率问题?
因为CallBase=true会让GenerateOutPut执行真实的MyClassN代码,而不是Moq生成的代理逻辑,所以代码覆盖率工具(比如OpenCover、Coverlet)会正常统计这部分代码的执行情况。
进阶方案:如果能修改BaseClass2的代码
如果BaseClass2是你可控的(不是第三方库),可以进一步通过提取依赖彻底解耦:
// 定义值提供接口 public interface IValueProvider { int GetValue(); } // 修改BaseClass2,注入依赖 abstract class BaseClass2 : BaseClass1 { protected readonly IValueProvider _valueProvider; protected BaseClass2(IValueProvider valueProvider) => _valueProvider = valueProvider; public new virtual int GetValue() => _valueProvider.GetValue(); } // 生产环境实现:调用BaseClass1的GetValue public class RealValueProvider : IValueProvider { private readonly BaseClass1 _baseInstance; public RealValueProvider(BaseClass1 baseInstance) => _baseInstance = baseInstance; public int GetValue() => base.GetValue(); }
这样所有MyClassN的测试只需要MockIValueProvider即可,完全不用关心BaseClass的层级结构,扩展性更好。
内容的提问来源于stack exchange,提问作者ryanxiao

