如何使用FakeItEasy伪造商用第三方库的非虚成员完成单元测试
解决方案
方案1:引入抽象适配层(优先推荐,开源无额外依赖)
这是最通用的解耦方案,完全避开对第三方类的直接依赖,不需要修改第三方库代码:
- 定义抽象接口,声明你需要用到的
Discovery类方法
public interface IDiscoveryService { List<ApplicationDescription> FindServers(string discoveryUrl); }
- 编写第三方
Discovery类的包装实现
public class DiscoveryServiceWrapper : IDiscoveryService { private readonly Discovery _realDiscovery; public DiscoveryServiceWrapper(ApplicationInstanceBase applicationInstance) { _realDiscovery = new Discovery(applicationInstance); } public List<ApplicationDescription> FindServers(string discoveryUrl) { return _realDiscovery.FindServers(discoveryUrl); } }
- 修改业务代码,改为依赖抽象接口而非具体实现
你可以通过构造函数注入IDiscoveryService的实例,或者注入工厂来按需创建:
// 业务类构造函数新增依赖注入 private readonly IDiscoveryService _discoveryService; public OpcClient(IDiscoveryService discoveryService) { _discoveryService = discoveryService; } public List<ApplicationDescription> FindServers(ApplicationInstanceBase applicationInstance, string serverName, string serverPort) { try { var discoveryUrl = "opc.tcp://" + serverName + ":" + serverPort; // 直接调用抽象接口的方法,不再直接实例化Discovery return _discoveryService.FindServers(discoveryUrl); } catch (Exception) { return new List<ApplicationDescription>(); } }
- 修改测试代码,直接伪造抽象接口即可
[Test] public void FindServersTest__simple() { _serverList = CreateServerList(); // 伪造接口不需要处理构造函数、非虚方法的问题 var fakeDiscovery = A.Fake<IDiscoveryService>(); A.CallTo(() => fakeDiscovery.FindServers(A<string>.Ignored)).Returns(_serverList); // 把伪造的接口注入被测业务类 var opcClient = new OpcClient(fakeDiscovery); var servers = opcClient.FindServers(_applicationInstance, _OPCServer, _OPCServerPort); Assert.AreEqual(2, servers.Count); }
优点:完全解耦第三方库,后续更换OPC库只需要修改包装层,单元测试逻辑稳定,完全兼容FakeItEasy等免费伪造框架
方案2:用支持拦截非虚方法的付费伪造框架
如果不想修改现有业务代码,可以使用支持非虚成员拦截的商用测试框架:
- Typemock Isolator
- Telerik JustMock
这类框架不需要修改业务代码,直接可以伪造非虚的第三方类方法,但是需要付费,且部分框架对.NET Core/5+的支持需要额外配置。
方案3:提取第三方调用逻辑为虚方法,最小改动适配测试
如果不想引入新的接口和包装类,可以对业务代码做最小改动:
- 把直接调用
Discovery的逻辑提取为受保护的虚方法
public List<ApplicationDescription> FindServers(ApplicationInstanceBase applicationInstance, string serverName, string serverPort) { try { var discoveryUrl = "opc.tcp://" + serverName + ":" + serverPort; return CallDiscoveryFindServers(applicationInstance, discoveryUrl); } catch (Exception) { return new List<ApplicationDescription>(); } } // 新增虚方法,封装所有第三方Discovery的调用逻辑 protected virtual List<ApplicationDescription> CallDiscoveryFindServers(ApplicationInstanceBase appInstance, string discoveryUrl) { Discovery discovery = new Discovery(appInstance); return discovery.FindServers(discoveryUrl); }
- 测试时直接伪造被测类的这个虚方法,返回模拟数据即可
[Test] public void FindServersTest__simple() { _serverList = CreateServerList(); // 伪造被测类的虚方法,配置默认调用基类方法避免其他逻辑被拦截 var fakeOpcClient = A.Fake<OpcClient>(x => x.CallsBaseMethods()); A.CallTo(() => fakeOpcClient.CallDiscoveryFindServers(A<ApplicationInstanceBase>.Ignored, A<string>.Ignored)).Returns(_serverList); var servers = fakeOpcClient.FindServers(_applicationInstance, _OPCServer, _OPCServerPort); Assert.AreEqual(2, servers.Count); }
优点:业务代码改动极小,不需要引入新的类结构
缺点:被测类的伪造需要注意配置调用基类方法,避免其他业务逻辑被拦截
内容的提问来源于stack exchange,提问作者sdmorris
相关产品推荐
相关产品推荐

