Oefeningen: Test Driven Development
Werk de oefeningen in volgorde. Schrijf bij elke oefening eerst de testlijst, dan de eerste test, dan de minimale implementatie, en refactor waar nodig. Volg de Red-Green-Refactor cyclus consequent.
Startpakket downloaden
De download wordt geladen…
Hierin staat alles wat je in de vorige lessen gebouwd hebt, samen met de code die je
tijdens de theorie van deze les opbouwt. Wat je in de oefeningen zelf moet schrijven,
staat erin als skelet met de melding // jouw code hier.
De webshop zit erbij. Je hoeft geen Razor te kennen: start hem met
dotnet run --project ShopWave.Web en open http://localhost:5000. Zo zie je meteen wat je code doet.
Oefening 1: CartService via TDD
Leerdoel: je bouwt een klasse volledig via TDD door de Red-Green-Refactor cyclus stap voor stap te doorlopen.
Moeilijkheidsgraad: basis
Startcode
Maak deze klasse aan in ShopWave. Je hoeft hem niet aan te passen, maar je hebt hem nodig als bouwsteen voor CartService.
namespace ShopWave
{
public class CartItem
{
public string Name { get; set; }
public double Price { get; set; }
public int Quantity { get; set; }
public CartItem(string name, double price, int quantity)
{
Name = name;
Price = price;
Quantity = quantity;
}
}
}
Stap 1: stel je testlijst op
Noteer alle testgevallen die je verwacht nodig te hebben voor CartService. Schrijf ze op voor je ook maar één lijn code aanraakt. De lijst hieronder is een vertrekpunt, maar je mag gevallen toevoegen als je ze ontdekt tijdens het implementeren.
[ ] Nieuw aangemaakt mandje heeft een totaal van 0
[ ] Eén artikel toevoegen verhoogt het totaal met de prijs van dat artikel
[ ] Meerdere artikelen optellen geeft het correcte totaal
[ ] Hoeveelheid meegeven vermenigvuldigt de prijs correct
[ ] Een artikel verwijderen verlaagt het totaal
[ ] Een negatieve hoeveelheid geeft een ArgumentException
[ ] Het mandje leegmaken reset het totaal naar 0
Stap 2: bouw CartService via TDD
In het startpakket staat CartService al klaar in ShopWave, met lege methodes AddItem en Total. Die twee staan er alleen in omdat de webshop de klasse anders niet kan compileren; de inhoud schrijf jij. Werk je zonder startpakket, maak dan zelf een lege CartService-klasse aan.
Maak daarnaast een testklasse CartServiceTests in ShopWave.Tests. Werk de testlijst van boven naar onder af. Doorloop voor elk geval de volledige Red-Green-Refactor cyclus voor je naar het volgende gaat.
Verwacht resultaat:
| Actie | Verwacht totaal |
|---|---|
| Leeg mandje | 0 |
| Laptop (999.99) toevoegen | 999.99 |
| Laptop + Muis (29.99) | 1029.98 |
| 4 pennen aan 2.50 | 10.00 |
| Laptop + Muis, dan Muis verwijderen | 999.99 |
| Laptop toevoegen, dan Clear() | 0 |
Negatieve hoeveelheid testen doe je zo:
Action act = () => cart.AddItem("Laptop", 999.99, -1);
act.Should().Throw<ArgumentException>();
Vastgelopen?Hulp bij Oefening 1: CartService via TDD
klik voor hulp
De assistent wordt geladen…
Oefening 2: CartService uitbreiden met couponondersteuning
Leerdoel: je combineert TDD met Dependency Injection en Moq om een uitbreiding te schrijven op een bestaande klasse.
Moeilijkheidsgraad: gemiddeld
Startcode
Moq werkt alleen met interfaces. Maak eerst deze interface aan in ShopWave:
namespace ShopWave
{
public interface ICouponService
{
bool IsValid(string code);
int GetDiscount(string code);
void MarkAsUsed(string code);
}
}
Laat CouponService uit de demo deze interface implementeren:
public class CouponService : ICouponService
Voeg daarna een constructor toe aan CartService die een ICouponService ontvangt, net zoals OrderService in Les 1 een IPaymentGateway ontving.
Opdracht
Breid CartService uit met een methode ApplyCoupon(string code). Schrijf eerst de testlijst, dan de tests, dan de implementatie.
Vereisten:
- Een geldige coupon verlaagt het totaal met het opgegeven percentage
- Een ongeldige coupon heeft geen effect op het totaal
- Na het toepassen van een coupon wordt die als gebruikt gemarkeerd via
MarkAsUsed - Dezelfde coupon kan niet tweemaal worden toegepast
Structuur van een test met twee mocks:
Mock<ICouponService> mockCoupon = new Mock<ICouponService>();
mockCoupon.Setup(c => c.IsValid("ZOMER10")).Returns(true);
mockCoupon.Setup(c => c.GetDiscount("ZOMER10")).Returns(10);
CartService cart = new CartService(mockCoupon.Object);
cart.AddItem("Laptop", 100.0);
cart.ApplyCoupon("ZOMER10");
cart.Total.Should().Be(90.0);
Verwacht resultaat:
| Situatie | Verwacht totaal bij basisprijs 100.00 |
|---|---|
| Geen coupon | 100.00 |
| Geldige coupon van 10% | 90.00 |
| Ongeldige coupon | 100.00 |
Controleer ook via Verify dat MarkAsUsed precies eenmaal aangeroepen wordt bij een geldige coupon, en nooit bij een ongeldige.
Vastgelopen?Hulp bij Oefening 2: CartService uitbreiden met couponondersteuning
klik voor hulp
De assistent wordt geladen…
Oefening 3: OrderService uitbreiden via TDD
Leerdoel: je past TDD toe op een bestaande klasse en combineert meerdere mocks in één test.
Moeilijkheidsgraad: uitdaging
Startcode
De OrderService uit Les 1 krijgt een extra parameter:
public string PlaceOrder(int productId, int quantity, double amount, string couponCode = "")
OrderService ontvangt nu ook een ICouponService via de constructor, naast IPaymentGateway en IStockService.
Opdracht
Opdracht
Stel eerst je testlijst op. Bouw daarna de uitbreiding via TDD.
Vereisten:
- Als geen coupon meegegeven wordt, verloopt de bestelling zoals in Les 1
- Een geldige coupon verlaagt het te betalen bedrag voor de betaling plaatsvindt
- Een ongeldige coupon heeft geen effect: de bestelling wordt toch geplaatst zonder korting
- Een al gebruikte coupon geeft de melding
"Coupon reeds gebruikt."terug en plaatst geen bestelling
Schrijf minstens vijf tests. Gebruik Moq voor IPaymentGateway, IStockService en ICouponService.
Verwacht resultaat per scenario:
| Situatie | Verwacht resultaat |
|---|---|
| Geen coupon, betaling geslaagd | "Bestelling bevestigd" |
| Geldige coupon, betaling geslaagd | "Bestelling bevestigd" (lager bedrag doorgestuurd naar gateway) |
| Ongeldige coupon, betaling geslaagd | "Bestelling bevestigd" (origineel bedrag) |
| Al gebruikte coupon | "Coupon reeds gebruikt." |
| Product niet op voorraad | "Product niet beschikbaar" |
Vastgelopen?Hulp bij Oefening 3: OrderService uitbreiden via TDD
klik voor hulp
De assistent wordt geladen…
Oefening 4: Reflectie
Beantwoord deze vragen voor jezelf voor je de oplossingen bekijkt.
- In stap 2 van de demo schreven we
return trueals volledige implementatie. Waarom is dat correct in TDD, ook al weet je dat het onvolledig is? - In stap 4 dwong de test ons om van een
List<string>naar eenList<Coupon>over te stappen. Wat zegt dit over hoe TDD het ontwerp beinvloedt? - Wat is het verschil tussen de refactorfase en de groene fase? Waarom mag je tijdens de groene fase niet refactoren?
- Beschrijf een moment tijdens het uitwerken van de oefeningen waarbij een test je dwong iets te ontdekken wat je niet had verwacht.
Controleer je werk in de webshop
Start de webshop met dotnet run --project ShopWave.Web en open http://localhost:5000. Zo zie je je eigen code draaien in plaats van alleen een groene testbalk.
| Wat je doet | Wat je ziet als je code klopt |
|---|---|
| Voeg een Laptop toe aan je mandje | Eén regel in de tabel en een totaal van 999,99 EUR |
| Voeg er een Muis bij | Twee regels en een totaal van 1029,98 EUR |
| Voeg nog een Laptop toe | Geen derde regel maar aantal 2, en een totaal van 2029,97 EUR |
Onder elk resultaat staat uit welke klasse het komt. Zie je iets anders dan hierboven, dan weet je meteen welke methode je moet nakijken.
De mandjespagina in het startpakket kan alleen toevoegen en optellen, want meer heeft
CartService op dat moment nog niet. Coupons, artikels verwijderen en het mandje legen
zie je in de webshop vanaf les 4, zodra je klasse af is.