Home > Software engineering >  Where do end-to-end or integration tests fit within a TDD worflow?
Where do end-to-end or integration tests fit within a TDD worflow?

Time:05-02

Everyone is familiar with the typical workflow of TDD: write a failing test, make the test pass by writing just enough code, refactor your code, and repeat. This is often captured with the coined "red, green, refactor".

The TDD workflow is most naturally practiced with unit tests. However, everyone also knows that those are not enough and a good code base should also be covered by integration and end-to-end tests. Can or should these other types of tests be implemented as part of the TDD workflow? Why or why not? If so, where do they fit within a development workflow practice by a typical agile team?

CodePudding user response:

Yes, you can use Outside-in TDD, where you start at the boundary, writing a coarse-grained test against the 'surface area' of the application. After that, you flesh out behaviour with integration and unit tests.

Here's an example of a boundary test against a REST API, written in C#:

[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);
}

The example is taken from my blog post Self-hosted integration tests in ASP.NET.

I routinely write such tests, starting at the boundary, and then following up with unit tests like this one:

[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);
}

This test bypasses the HTTP layer to instead target a particular class (ReservationsController) - a unit test.

This technique is quite well described by Nat Pryce and Steve Freeman in their 2009 book Growing Object-Oriented Software, Guided by Tests.

  • Related