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

如何重构含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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 09:44:55