【问题标题】:How to call a dependency injection class method in unit test?如何在单元测试中调用依赖注入类方法?
【发布时间】:2022-01-28 18:06:12
【问题描述】:

我是单元测试和 DI 的新手,我找不到在使用依赖注入设计的类中调用方法的简单方法。

这是我的课

public class AgentProvisioningServiceHelpher : IAgentProvisioningServiceHelpher
{
    private readonly IExcelParser _excelParser;
    private readonly SupervisorDbContext _SupervisorDbContext;
    private readonly SchedulerNoTrackingDbContext _SchedulerDbContext;

    // constructor
    public AgentProvisioningServiceHelpher(IExcelParser excelParser, SupervisorDbContext supervisorDbContext, SchedulerNoTrackingDbContext SchedulerDbContext)
    {
        _excelParser = excelParser;
        _SupervisorDbContext = supervisorDbContext;
        _SchedulerDbContext = SchedulerDbContext;
    }

    // Function that I want to call in unit test
    public int SimpleMethodToTest(int InputId) 
    {
        return InputId + 1;
    } 
}

这是我的界面代码

    public interface IAgentProvisioningServiceHelpher
    {
        int SimpleMethodToTest(int InputId);
    }

这是我的单元测试代码,我正在使用 Xunit

public class UnitTest1
{
    private IAgentProvisioningServiceHelpher _sut; 
    private IExcelParser _excelParser;
    private SupervisorDbContext _DBcontext1;
    private SchedulerNoTrackingDbContext _DBcontext2;

    public UnitTest1(IExcelParser excelParser, SupervisorDbContext DBcontext1, SchedulerNoTrackingDbContext DBcontext2, IAgentProvisioningServiceHelpher sut)
    {
        _excelParser = excelParser;
        _DBcontext1 = DBcontext1;
        _DBcontext2 = DBcontext2;
        _sut = sut;
    }

    [Fact]
    public void SimpleMethodToTest_Shall_ReturnPlus1()
    {
        // Arrange
        int Input_Int = 1;

        // Act
        // I try to tell the interface to map with the class I want to test
        IAgentProvisioningServiceHelpher _sut = new AgentProvisioningServiceHelpher(_excelParser, _DBcontext1, _DBcontext2);

        // Then I try to call the interface method 
        var result = _sut.SimpleMethodToTest(Input_Int);

        // Assert
        Assert.Equal(2, result);
    }
}

当我尝试运行测试时,Visual Studio 报错 - 我该如何解决这个问题?

UnitTest1.cs 第 33 行

以下构造函数参数没有匹配的夹具数据:IExcelParser excelParser、SupervisorDbContext DBcontext1、SchedulerNoTrackingDbContext DBcontext2、IAgentProvisioningServiceHelpher sut

【问题讨论】:

标签: c# unit-testing dependency-injection xunit.net


【解决方案1】:

应用 DI 时,您将创建依赖关系推迟到最后一个负责任的时刻。这意味着您尽可能长时间地推动创建依赖关系的负担。但是在应用程序的某个地方,需要创建这些依赖项。

这个组合依赖的地方叫做Composition Root。对于您正在运行的应用程序,Composition Root 可能是应用程序的 Main 方法,或者至少是靠近应用程序启动路径的某个位置。

单元测试中的组成

在编写单元测试时,每个单元测试本身都充当组合根。这意味着单元测试本身(或它调用的方法)本身负责它需要测试的类的组合。这意味着组合不再被推迟,也不再被推高。这就是您正在尝试做的事情:将对象组合的责任推给单元测试框架。

虽然从技术上讲,一些单元测试框架允许您拦截创建测试类的方式,让测试框架提供依赖项通常没有什么意义,因为单元测试本身需要控制正在创建的确切依赖项。测试不仅知道依赖项的确切类型应该是什么(即通常是某种假实现),而且还需要配置那些(假)依赖项或查询它们的结果以断言测试。

这意味着,与其尝试在UnitTest1 的构造函数中注入AgentProvisioningServiceHelpher 的依赖关系,不如必须控制SimpleMethodToTest_Shall_ReturnPlus1 方法。例如:

[Fact]
public void SimpleMethodToTest_Shall_ReturnPlus1()
{
    // Arrange
    int input = 1;
    int expectedResult = 2;

    var sut = new AgentProvisioningServiceHelpher(
        new FakeExcelParser(),
        new FakeSupervisorDbContext(),
        new FakeSchedulerNoTrackingDbContext());

    // Act
    var actualResult = sut.SimpleMethodToTest(input);

    // Assert
    Assert.Equal(expectedResult, actualResult);
}

不幸的是,在每个单元测试中创建被测类的所有依赖项,编写的测试越多,可维护性就越差。在这种情况下,将这种组合逻辑从测试中提取到辅助方法或辅助类中成为一种好习惯。在这种情况下,诀窍是确保测试只提供对该特定测试特别感兴趣的依赖项,而将其余部分留空。例如:

[Fact]
public void Parser_should_always_be_called()
{
    // Arrange
    var parser = new FakeExcelParser();

    AgentProvisioningServiceHelpher sut = this.CreateSut(excelParser: parser);

    // Act
    sut.SimpleMethodToTest(0);

    // Assert
    Assert.IsTrue(parser.GotCalled);
}

private AgentProvisioningServiceHelpher CreateSut(
    IExcelParser excelParser = null,
    SupervisorDbContext supervisorDbContext = null,
    SchedulerNoTrackingDbContext schedulerDbContext = null)
{
    return new AgentProvisioningServiceHelpher(
        excelParser ?? new FakeExcelParser(),
        supervisorDbContext ?? new FakeSupervisorDbContext(),
        schedulerDbContext ?? new FakeSchedulerNoTrackingDbContext());
}

在此测试中,仅提供ExcelParser,因为它在测试期间被显式查询。其他两个依赖项将由CreateSut 方法提供一个默认(可能是假的)空实现。

在这种情况下,CreateSut 成为合成根的一部分。

集成测试中的组成

在编写单元测试时,依赖项通常是手动连接的,如上所示。但是,如果您正在编写集成测试,测试中涉及的对象数量通常会更大,并且需要类似于在生产应用程序中组合的对象结构(有时会替换一些依赖项)。让单个测试方法或测试类手动重新创建完整的对象结构通常很麻烦,而且容易出错。应用程序对象结构的更改可能会通过许多测试,并且很容易导致难以维护的系统。

相反,在集成测试期间,通常会尝试重用正在运行的应用程序的组合根使用的相同对象组合逻辑。当您使用 DI 容器来组成应用程序的对象图时,这通常意味着重用相同的 DI 容器注册。

集成测试将重用相同的 DI 容器的配置,模拟运行集成测试所需的一些依赖项,解析被测类并调用其方法之一。但是,集成测试仍然不会从外部注入这些依赖项,因为它可能需要对所创建的内容进行一些控制。集成测试仍然是它自己的合成根,即使它将对象合成的一部分委托给单独的 Composer 类(DI 容器)。

这是一个集成测试的示例:

[Fact]
public void Some_integration_test()
{
    // Arrange
    int input = 1;
    int expectedResult = 2;

    // Mock object
    var parser = new FakeExcelParser();

    // Create a valid container to resolve object graphs from
    var container = TestBootstrapper.BuildContainer();

    // Configure it especially for this test (note that I'm inventing a
    // DI Container API here. API will very per DI Container)
    container.Replace<IExcelParser>(parser);

    // Resolve the SUT from the DI Container
    var sut = container.Resolve<AgentProvisioningServiceHelpher>();

    // Act
    var actualResult = sut.SimpleMethodToTest(input);

    // Assert
    Assert.Equal(expectedResult, actualResult);
}

此集成测试使用可能在集成测试之间共享的TestBootstrapper 类:

public static class TestBootstrapper
{
    public static Container BuildContainer()
    {
        // Request a fully configured DI Container instance from the
        // actual application. This ensures that the integration test
        // runs using the exact same object graphs as the final application.
        var container = RealApplication.Bootstrapper.BuildContainer();

        // Replace dependencies that should never be used during the
        // integration tests.
        container.Replace<IHardDiskFormatter, FakeDiskFormatter>();
        container.Replace<ISmsSender, FakeSmsSender>();
        container.Replace<IPaymentProvider, FakePaymentProvider>();

        return container;
    }
}

这当然与高度隔离的单元测试非常不同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-19
    • 1970-01-01
    • 2021-05-21
    • 1970-01-01
    • 2017-04-01
    • 2021-05-17
    相关资源
    最近更新 更多