Testgestuurde ontwikkeling (TDD) is een softwareontwikkelingsproces dat door Kent Beck is geïntroduceerd als onderdeel van de extreme programmeermethodologie. Bij TDD worden eerst unit tests geschreven voordat implementaties worden ontwikkeld.
De eisen worden vastgelegd als testgevallen. Wanneer nieuwe eisen aan de software worden toegevoegd, worden ook nieuwe testgevallen geschreven. Terwijl het systeem wordt ontwikkeld, bouwen we een groter beeld op van de eisen en vergroten we de dekking van de unit tests.
Het door TDD aanbevolen proces is als volgt:
Een belangrijk punt hierbij is dat de tests eerst worden toegevoegd voordat de functionaliteit wordt ontwikkeld. Door de eenvoudigste oplossing te schrijven slaagt de test, waarna deze door refactoring kan worden verfijnd. De tests zorgen er dan voor dat eventuele refactoring geen defecten heeft geïntroduceerd.
Testgestuurde ontwikkeling levert meestal code van betere kwaliteit op en vermindert de kans op defecten. Het stimuleert het gebruik van kleine iteraties bij de ontwikkeling van software.
Een nuttig neveneffect is dat de tests dienen als een vorm van documentatie voor de eisen van de software.
Code ontwikkeld onder TDD kan leiden tot flexibele en uitbreidbare code. Dit komt doordat ontwikkelaars vaak moeten denken in termen van kleinere eenheden die geïsoleerd getest en geïntegreerd kunnen worden. Omdat alleen de minimale hoeveelheid code wordt toegevoegd om de tests te laten slagen, is de kans groter dat de tests zelf alle codepaden in de software bestrijken.
Indien gebruikt in combinatie met een broncontrolesysteem, kan in het geval van falende tests de code worden teruggerold naar de laatste versie met een volledige set tests die geslaagd is.
Hoewel testgestuurde ontwikkeling gemakkelijk te gebruiken is bij nieuwe projecten, is het moeilijk toe te passen op bestaande of legacysystemen. Als nieuwe code wordt toegevoegd aan legacy software, dan kunnen deze nieuwe onderdelen uiteraard worden ontwikkeld met TDD, maar het toevoegen van tests aan legacy code kan moeilijk en tijdrovend zijn.
Sommige processen zijn moeilijk te testen, zoals User Interface processen of database connectiviteit.
Een groot aantal succesvolle tests kan een vals gevoel van veiligheid geven, waardoor andere activiteiten onvoldoende worden getest.
Het ontwikkelen en onderhouden van een grote hoeveelheid tests kan een tijdrovend proces zijn.
TDD kan eenvoudig worden gerealiseerd met DUnitX, het unit testing framework dat met Delphi wordt meegeleverd. Met de in Delphi ingebouwde wizard is het eenvoudig om een DUnitX-testproject aan te maken, compleet met een testunit en enkele voorbeeldtests.
Het schrijven van een falende test kan zo eenvoudig zijn als het schrijven van een lege test die geen enkele functionaliteit aanroept:
unit Tests.VATCalculator; interface uses DUnitX.TestFramework; type [TestFixture] TTestVATCalculator = class public [Test] [TestCase ('100_Pounds', '100,20')] procedure TestGetVATAmount(const NetAmount: Currency; const ExpectedAmount: Currency); end; implementation procedure TTestVATCalculator.TestGetVATAmount(const NetAmount: Currency; const ExpectedAmount: Currency); begin var ReturnValue: Currency; // No implementation Assert.AreEqual(ExpectedAmount, ReturnValue); end; initialization TDUnitX.RegisterTestFixture(TTestVATCalculator); end.
Als deze test wordt uitgevoerd, zal de test mislukken. Implementeer de functionaliteit voor de test met zo eenvoudig mogelijke code:
unit VAT.Calculator.Interfaces; interface type IVATCalculator = interface ['{1576AC66-A116-47EB-9C6A-23D4E2C21B2C}'] function GetVAT(const NetAmount: Currency): Currency; end; implementation end.
unit VAT.Calculator; interface uses VAT.Calculator.Interfaces; type TVATCalculator = class(TInterfacedObject, IVATCalculator) public function GetVAT(const NetAmount: Currency): Currency; end; implementation function TVATCalculator.GetVAT(const NetAmount: Currency): Currency; begin Result := NetAmount * 20 / 100; end; end.
unit Tests.VATCalculator; interface uses DUnitX.TestFramework, VAT.Calculator.Interfaces; type [TestFixture] TTestVATCalculator = class private FVATCalculator: IVATCalculator; public [Setup] procedure Setup; [Test] [TestCase ('100_Pounds', '100,20')] procedure TestGetVATAmount(const NetAmount: Currency; const ExpectedAmount: Currency); end; implementation uses VAT.Calculator; procedure TTestVATCalculator.Setup; begin FVATCalculator := TVATCalculator.Create; end; procedure TTestVATCalculator.TestGetVATAmount(const NetAmount: Currency; const ExpectedAmount: Currency); var ReturnValue: Currency; begin ReturnValue := FVATCalculator.GetVAT(NetAmount); Assert.AreEqual(ExpectedAmount, ReturnValue); end; initialization TDUnitX.RegisterTestFixture(TTestVATCalculator); end.
De tests zullen nu slagen en het proces kan worden voortgezet om meer eisen toe te voegen en waar nodig te refactoren.
Testgestuurde ontwikkeling is een krachtig hulpmiddel om eisen vast te leggen en ervoor te zorgen dat tests tegelijk met de software worden ontwikkeld. Het wordt het best gebruikt voor nieuwe projecten of voor het toevoegen van nieuwe modules aan bestaande projecten. Hoewel het tijdrovend kan zijn, bieden de tests een waardevol mechanisme om ervoor te zorgen dat nieuwe toevoegingen aan de software geen bestaande functionaliteit breken. Het blijft een zeer nuttig instrument in de gereedschapskist van een software-ontwikkelaar.
Contact