Delphi Test-Driven Development (TDD) and DUnitX

Guidelines on how the AI ​​should act and code when the user requests TDD, unit tests, DUnitX or fakes/mocks using Interfaces in Delphi.

SKILL.md

GitHub

Test-Driven Development (TDD) in Delphi with DUnitX

This skill guides behavioral expectations for test-driven development (TDD) using the modern Delphi ecosystem.

When operating under the scope of TDD, AI MUST ALWAYS prioritize the Red-Green-Refactor cycle. If the user requests TDD, DO NOT write the business implementation before writing the test that will fail.

The Red-Green-Refactor Cycle (Interaction Rules)

  1. Red (Failed Test): Start by declaring the skeleton of the target class/interface in the interface section just to compile. Then, immediately write a complete DUnitX Test Case calling out the non-existent behavior or asserting an expected result. The test will logically fail.
  2. Green (Minimal Code): Write the minimum and raw actual implementation, enough to make the test Assert pass.
  3. Refactor (Cleaning): Improve the code (Clean Code, duplication removal, optimizations) ensuring that the test does not break.

DUnitX Best Practices

Test Structure

  • Test Class: The test case must be annotated with [TestFixture].
  • Setup and TearDown: Use [Setup] to instantiate Classes and Fakes. Use [TearDown] to clean up instances that do not use ARC (Interfaces).
  • No Memory Leaks in Tests: [TearDown] and injection via Interface (ARC) are mandatory to keep the suite watertight.

Nomenclature of Test Methods

Embrace context conventions like Action_Condition_ExpectedResult:

[Test]
procedure ComputeDiscount_LoyalCustomer_ReturnsTenPercent;

Modern Assertions

Replace manual Boolean validations (Assert.IsTrue(A = B)) with fluent and specific Assert:

  • Assert.AreEqual(100.0, FInvoice.Total)
  • Assert.IsNotNull(FCustomer)
  • Assert.WillRaise(procedure begin FSut.DoInvalid; end, EBusinessRuleException)
  • Assert.Contains('Error', LMessage)

Dependency Injection and Mocks (Test Doubles)

To isolate the class under test (SUT - System Under Test) from the infrastructure (Database, APIs, View), apply Strict Dependency Inversion (DIP) by injecting Interfaces into the SUT via constructor.

As Delphi does not have a built-in Mocking Framework in RTL, write local "Fake/Mock" Classes implementing the Interface to simulate the dependency within the test's implementation session.

//Fake is created only in the test file
TFakeEmailService = class(TInterfacedObject, IEmailService)
public
  SentCount: Integer;
  procedure Send(const AMsg: string);
end;

AntiPatterns that AI should Avoid

  1. ❌ Direct coupling to the database (TFDQuery) in the tested class. Always abstract database access into a IRepository and create a TFakeRepository for DUnitX.
  2. ❌ Test UI. Restrict the scope of DUnitX to the Domain and Application Services Layer.
  3. ❌ Write the entire class along with the tests. The user must be followed step-by-step in TDD. If the AI ​​is required to provide everything, send the Tests first in the output.
  4. ❌ Swallowing Exceptions (try..except on E: Exception do) in methods being tested, this breaks Assert.WillRaise().