使用中央 DI 逻辑进行单元测试和最小化 "Arrange" 阶段

Unit testing and minimizing "Arrange" phase with central DI logic

我的单元测试唯一的问题是我的 Arrange 阶段(在 Arrange-Act-Assert pattern 中)很长,因此出现了可维护性问题。我在测试 classes 的构造函数中进行了数十次注册。一些测试 classes 有共同的注册,我认为我可以将所有这些注册移动到一个中心位置并在其上使用 AssemblyInitialize 属性(例如,这种方法就像 Global.asaxStartup.cs)。如果有人想覆盖注册的类型,它可以简单地重新注册它(在这种情况下,我的容器覆盖了旧的注册类型)。但是,一些测试也会有一些额外的注册 classes。

那会是个糟糕的决定吗?如果是这样,为什么?

是否有任何第三方工具可以帮助我(也许以更好的方式)?

这样做会影响我的单元测试时间吗?因为有些测试 classes 只有几个注册。我是否应该使用一些东西来为他们忽略这些中央注册?

编辑:

示例代码:

private CustomersController controller;

public CustomersControllerTests()
{
    var container = ContainerFactory.GetContainer();
    MapperConfiguration autoMapperConfig = new MapperConfiguration(cfg =>
    {
        cfg.AddProfile<DefaultMappingProfile>();
    });
    container.RegisterInstance(autoMapperConfig);
    container.Register<Common.Contracts.Mapper.IMapper, AutoMapperWrapped>(Lifetime.Singleton);
    container.Register<IProxy, BusinessProxy>();
    container.Register<IDataContext, FakeDataContext>();
    container.Register<IUnitOfWork, FakeUnitOfWork>();
    container.Register<ICustomerBusiness, CustomerBusiness>();
    //...Some more registrations
    controller = new CustomersController();
} 

[TestMethod]
public async Task GetCustomerById_ForNonExistingCustomer_ReturnsNotFound()
{
    //Act
    var result = await controller.GetCustomerById(0);

    //Assert
    Assert.IsInstanceOfType(result, typeof(NotFoundResult));
}

对于此测试方法,Arrange 部分位于测试的构造函数中 class,如图所示。

您的单元测试正在测试单个单元 - 即 class。理想情况下,任何依赖项都应该是模拟的,您可以完全控制从它们返回的内容。

SUT Builder 模式是一个值得遵循的好模式,尽管有些人不同意。 Mark Seemann 在 Pluralsight 上的高级单元测试课程中对此进行了精彩的解释 - 任何想要进行单元测试的人 - 我都会尝试将他们指向这门课程。

Advanced Unit Testing

SUT Builder 模式是一种 Fluent API 模式,您基本上可以使用它来创建被测系统 (SUT) - 即,class 正在接受单元测试。这可以提高测试的可读性并保持干燥(不要重复自己)。

快速示例

internal sealed class MySutBuilder
{
    private ISignInService _webServiceSignInService;


    public MySutBuilder()
    {
        _webServiceSignInService = new Mock<ISignInService>().Object;
    }

    public MySutBuilder WithSecurityAccess()
    {
        var authenticationMock = new Mock<ISignInService>();
        authenticationMock.Setup(m => m.SignIn(It.IsAny<GetDocumentContentRequest>()))
                    .Returns(SignInResult.Success(new ServiceUserContextMock()));

           _webServiceSignInService = authenticationMock.Object;

        return this;
    }

    public MyService Build()
    {
        MyService cut = new MyService(_webServiceSignInService);

        return cut;
    }
}

所以这个想法是在单元测试中使用 SutBuilder 来控制 class 的依赖关系。如果您向构造函数添加内容,那么您只需要在测试中的一个地方修复它 - 但我想这将取决于您的测试如何进行。

这是一段精简的代码,因此我们拥有的 classes 可以变得相当大,每个 class.

有 5 个以上的依赖项

单元测试用法

// arrange
var sut = new MySutBuilder()
    .WithSecrityAccess()
    .Build();

// act
var result = sut.DoSomething();

// assert