单元测试基于状态的测试

Unit testing state based test

假设我有一个名为 CreateApplication 的方法和一个私有辅助方法 GenerateApplication,它生成带有计算字段的应用程序对象。之后,我将生成的应用程序对象插入到数据库中。

public async Task<Guid> CreateApplication(CreateApplicationdRequest request)
{
    var application = GenerateApplication(request);

    await UnitOfWork.Application.InsertAsync(application); // it's same as _db.Application.InsertAsync(application) 
    await UnitOfWork.CommitAsync();  // it's same as _db.SaveChangesAsync();

    return application.Id;
}

对于这种情况,什么是合适的单元测试?我是否应该验证 InsertAsync 方法(将应用程序插入数据库)并检查应用程序对象是否正确生成?

像这样:

[Fact]
public async Task CreateApplication_WhenCalled_ShouldStoreApplication()
{
    // I have already mocked unitofwork in constructor
    // when InsertAsync method will called, it will assign passed argument to declared private field (application)
    Application application;
    UnitOfWork.Setup(x => x.Application.InsertAsync(It.IsAny<Application>()))
        .Callback<Application>(app => application = app);

    //act
    await _applicationService.CreateApplication(new CreateApplicationdRequest()); // dummy request

    //assert 
    UnitOfWork.Verify(x => x.Application.InsertAsync(It.IsAny<Application>()), Times.Once);
    UnitOfWork.Verify(x => x.CommitAsync(), Times.Once);

    //here I am checking generated application's state
    Assert.Equal(application.CustomerId, request.CustomerId);
    Assert.Equal(application.Applicant.PhoneNumber, request.PhoneNumber);
    Assert.Equal(application.LoanCurrencyId, request.LoanCurrencyId);
    Assert.Equal(application.TermType, request.TermType);
    Assert.Equal(application.Term, request.Term);
    Assert.Equal("01017054322_CC-01001", application.DocumentNo);
    Assert.Equal(application.GracePeriod, request.DefaultGracePeriod);
}

或者这不是正确的方法,对于这种情况最好使用集成测试?

有几种方法可以对方法进行单元测试。最流行的两种是黑盒和白盒测试。

黑盒测试

您对实现一无所知。您只关心输出(包括 return 值、输出参数和 side-effects)。因此,您想确保无论何时使用给定输入调用它,您都会看到预期的输出。

在那种情况下,您的测试将只检查 returned Guid 并验证应用程序是否存储在模拟存储中。

Assert.NotEqual(result, Guid.Empty);
Assert.NotNull(x.Application.FirstOrDefault(app => app.Id == result));

这种方法可以真正轻松地扩展到集成测试,而不是使用模拟存储,您可以使用存储的沙盒版本。

白盒测试

你知道你的方法是如何实现的。并且您要确保您的代码不会执行任何不需要的操作,例如在 GenerateApplicationInsertAsync 方法调用之间覆盖应用程序的某些属性。

这可能是好事也可能是坏事,这取决于您如何看待它。假设您需要规范化 PhoneNumber 并且出于任何原因在 CreateApplication 中执行此操作。从黑盒的角度来看,这无关紧要(无需更新您的测试),因为功能没有改变。从白盒角度来看,您需要调整测试以模拟规范化器并验证其调用并断言预期输出。

前者从回归的角度可以看作是安全网,而后者可以看作是重构的安全网。