如何在单元测试中调用依赖注入类方法?

HO *_*Pin 7 c# unit-testing dependency-injection xunit.net

我是单元测试和 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;
    } 
}
Run Code Online (Sandbox Code Playgroud)

这是我的界面代码

    public interface IAgentProvisioningServiceHelpher
    {
        int SimpleMethodToTest(int InputId);
    }
Run Code Online (Sandbox Code Playgroud)

这是我的单元测试代码,我使用的是 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当我尝试运行测试时,Visual Studio 抱怨此错误 - 我该如何解决此问题?

UnitTest1.cs 第 33 行

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

Ste*_*ven 11

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

组合依赖项的地方称为组合根。对于正在运行的应用程序,组合根可能是应用程序的Main方法,或者至少是应用程序启动路径附近的某个位置。

单元测试中的组合

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

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

这意味着该方法必须处于控制之中,而不是尝试将 的依赖项注入 的AgentProvisioningServiceHelpher构造函数内部。例如:UnitTest1SimpleMethodToTest_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);
}
Run Code Online (Sandbox Code Playgroud)

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

[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());
}
Run Code Online (Sandbox Code Playgroud)

在此测试中,仅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);
}
Run Code Online (Sandbox Code Playgroud)

此集成测试使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这当然与单元测试有很大不同,单元测试具有高度的隔离性。