端到端或集成测试在 TDD 工作流中的什么位置?

Where do end-to-end or integration tests fit within a TDD worflow?

每个人都熟悉 TDD 的典型工作流程:编写一个失败的测试,通过编写足够的代码使测试通过,重构代码,然后重复。这通常用创造的“红色、绿色、重构”来捕捉。

TDD 工作流最自然地通过单元测试进行实践。但是,每个人也都知道这些还不够,一个好的代码库还应该包括集成和端到端测试。这些其他类型的测试能否或应该作为 TDD 工作流程的一部分实施?为什么或者为什么不?如果是这样,它们在典型敏捷团队的开发工作流实践中的什么位置?

是的,您可以使用 Outside-in TDD,从边界开始,针对 'surface area' 编写 coarse-grained 测试应用程序。之后,您可以通过集成和单元测试充实行为。

这是一个用 C# 编写的针对 REST API 的边界测试示例:

[Fact]
public async Task ReserveTableAtNono()
{
    using var api = new SelfHostedApi();
    var client = api.CreateClient();
    var at = DateTime.Today.AddDays(434).At(20, 15);
    var dto = Some.Reservation.WithDate(at).WithQuantity(6).ToDto();
 
    var response = await client.PostReservation("Nono", dto);
 
    await AssertRemainingCapacity(client, at, "Nono", 4);
    await AssertRemainingCapacity(client, at, "Hipgnosta", 10);
}

示例取自我的博客post Self-hosted integration tests in ASP.NET.

我经常编写这样的测试,从边界开始,然后跟进像这样的单元测试:

[Fact]
public async Task PostToAbsentRestaurant()
{
    var restaurantDB = new InMemoryRestaurantDatabase(Some.Restaurant);
    var sut = new ReservationsController(
        new SystemClock(),
        restaurantDB,
        new FakeDatabase());
    var absentRestaurantId = 4;
    var r = await restaurantDB.GetRestaurant(absentRestaurantId);
    Assert.Null(r);

    var actual =
        await sut.Post(absentRestaurantId, Some.Reservation.ToDto());

    Assert.IsAssignableFrom<NotFoundResult>(actual);
}

此测试绕过 HTTP 层,转而针对特定的 class (ReservationsController) - 单元测试。

Nat Pryce 和 Steve Freeman 在他们 2009 年出版的书中对此技术进行了很好的描述 Growing Object-Oriented Software, Guided by Tests