如何重构含using语句的.Net遗留代码以编写单元测试?
解决.NET遗留代码中using语句内对象无法用FakeItEasy Mock的问题
问题核心
你这段代码里直接硬编码实例化ExcelPackage,属于紧耦合第三方依赖,导致单元测试时无法用FakeItEasy替换成Mock对象,进而覆盖不了using块内的业务逻辑。
具体解决方案:依赖抽象+包装类改造
1. 抽象核心依赖接口
先把ExcelPackage的核心行为抽象成接口(using语句要求对象实现IDisposable,接口需继承该接口):
public interface IExcelPackage : IDisposable { ExcelWorkbook Workbook { get; } }
后续需要更多ExcelPackage的方法时,再逐步添加到接口中。
2. 实现包装类封装真实逻辑
创建包装类,封装原生ExcelPackage的逻辑并实现上面的接口:
public class ExcelPackageWrapper : IExcelPackage { private readonly ExcelPackage _innerPackage; public ExcelPackageWrapper(FileInfo sourceFile) { _innerPackage = new ExcelPackage(sourceFile); } public ExcelWorkbook Workbook => _innerPackage.Workbook; public void Dispose() { _innerPackage.Dispose(); } }
3. 引入工厂接口解耦实例化
由于ExcelPackage是在方法内临时创建的(using块),用工厂模式解耦实例化逻辑:
public interface IExcelPackageFactory { IExcelPackage Create(FileInfo sourceFile); } public class ExcelPackageFactory : IExcelPackageFactory { public IExcelPackage Create(FileInfo sourceFile) { return new ExcelPackageWrapper(sourceFile); } }
4. 修改遗留代码注入依赖
将原类改为通过构造函数注入IExcelPackageFactory,替换硬编码的new ExcelPackage:
// 假设原类结构如下 public class IndustryService { private readonly IIndustryBAL _industryBAL; private readonly IExcelPackageFactory _excelPackageFactory; // 构造函数注入依赖(若未使用DI,可先手动new工厂实例,后续逐步迁移) public IndustryService(IIndustryBAL industryBAL, IExcelPackageFactory excelPackageFactory) { _industryBAL = industryBAL; _excelPackageFactory = excelPackageFactory; } public void YourOriginalMethod(int industryId, int userEnterpriseId, FileInfo sourceHierarchyFile) { // 替换为工厂创建实例 using (var package1 = _excelPackageFactory.Create(sourceHierarchyFile)) { ExcelWorksheet worksheet = package1.Workbook.Worksheets[0]; var result = _industryBAL.SaveSourceHierarchy(industryId, userEnterpriseId, worksheet); } } }
单元测试示例(FakeItEasy实现)
现在可以轻松Mock所有依赖,覆盖using块内的逻辑:
[Test] public void YourOriginalMethod_ShouldCallSaveSourceHierarchyWithCorrectWorksheet() { // 测试数据准备 var industryId = 1; var userEnterpriseId = 100; var sourceFile = new FileInfo("test.xlsx"); // Mock所有依赖对象 var mockFactory = A.Fake<IExcelPackageFactory>(); var mockPackage = A.Fake<IExcelPackage>(); var mockWorkbook = A.Fake<ExcelWorkbook>(); var mockWorksheet = A.Fake<ExcelWorksheet>(); var mockIndustryBAL = A.Fake<IIndustryBAL>(); // 设置Mock调用返回规则 A.CallTo(() => mockFactory.Create(sourceFile)).Returns(mockPackage); A.CallTo(() => mockPackage.Workbook).Returns(mockWorkbook); A.CallTo(() => mockWorkbook.Worksheets[0]).Returns(mockWorksheet); // 实例化被测类 var service = new IndustryService(mockIndustryBAL, mockFactory); // 执行被测方法 service.YourOriginalMethod(industryId, userEnterpriseId, sourceFile); // 验证行为符合预期 A.CallTo(() => mockFactory.Create(sourceFile)).MustHaveHappenedOnceExactly(); A.CallTo(() => mockPackage.Dispose()).MustHaveHappenedOnceExactly(); A.CallTo(() => mockIndustryBAL.SaveSourceHierarchy(industryId, userEnterpriseId, mockWorksheet)) .MustHaveHappenedOnceExactly(); }
额外注意点
- 如果
ExcelWorkbook、ExcelWorksheet是密封类(比如部分EPPlus版本),FakeItEasy无法直接Mock,需对它们做同样的接口+包装类改造 - 包装类仅封装当前业务需要的属性/方法,避免过度封装增加维护成本
- 若遗留代码未使用依赖注入容器,可先在构造函数中手动
new ExcelPackageFactory(),后续再逐步迁移到DI容器
内容的提问来源于stack exchange,提问作者indrajit bagchi
相关产品推荐
相关产品推荐

