Red -> Green -> Refactor: first I write a failing test that describes the behavior, then the minimum code to make it pass, then I refactor with the test as a safety net.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | // ====================================================================== // Q: Do you practice TDD? Show me. // // Red -> Green -> Refactor: first I write a failing test that describes the // behavior, then the minimum code to make it pass, then I refactor with the test // as a safety net. Benefits: it pushes toward good design (testable code is // loosely coupled), it serves as living documentation and it protects against // regressions. // - Test pyramid: many fast unit tests (domain), fewer integration tests // (WebApplicationFactory + Testcontainers, Q13), a few E2E tests. // - Tools: xUnit / NUnit, NSubstitute or Moq, Testcontainers, Verify (snapshot // tests), Stryker.NET (mutation testing: checks whether the tests actually // catch bugs). // - Mock only at the boundaries (ERP, email, time), not the DbContext. Test // names describe behavior: Behavior_condition_expected. // TDD with AI: I write the tests (they are the spec) and the agent implements // until they are green. If the AI writes both the tests and the code, the tests // merely confirm whatever it wrote (Q2). // ====================================================================== // Domain unit tests (red-green-refactor) public class OrderTests { [Fact] // 1. RED: the rule does not exist yet public void Cannot_add_line_to_placed_order() { // Arrange var order = OrderBuilder.Draft().WithLine("SKU-1", 2).Build(); order.Place(); // Act var act = () => order.AddLine("SKU-2", 1, new Money(10m, "EUR")); // Assert Assert.Throws<DomainException>(act); } [Theory] [InlineData(0)] [InlineData(-3)] public void Quantity_must_be_positive(int qty) { var order = OrderBuilder.Draft().Build(); Assert.Throws<DomainException>(() => order.AddLine("SKU-1", qty, new Money(10m, "EUR"))); } } // 2. GREEN: add the check to Order.AddLine (Q19) // 3. REFACTOR: clean up the code, the tests stay green // Application service test, with fakes only at the boundaries [Fact] public async Task Placing_an_order_saves_it_and_raises_OrderPlaced() { var order = OrderBuilder.Draft().WithLine("SKU-1", 2).Build(); var repo = Substitute.For<IOrderRepository>(); // NSubstitute repo.GetAsync(order.Id, Arg.Any<CancellationToken>()).Returns(order); var uow = Substitute.For<IUnitOfWork>(); var sut = new PlaceOrderHandler(repo, uow); await sut.Handle(new PlaceOrder(order.Id), CancellationToken.None); Assert.Equal(OrderStatus.Placed, order.Status); Assert.Contains(order.Events, e => e is OrderPlaced); await uow.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>()); } |