Brandbrand
Plan een gesprek
Brandbrand
Een bril voor een scherm vol code, met de code scherp door de glazen

Wat unit tests zijn, en hoe wij testen voor de oplevering

Een unit test controleert één klein stuk code, los van de rest. Wat dat oplevert, waar het ophoudt, en hoe wij Pest en CI gebruiken voordat er iets live gaat.

Eén stuk code, één verwachting

Een unit test pakt het kleinste stuk code dat iets zinnigs doet, meestal één functie of klasse, en controleert het met een vaste invoer en een verwachte uitkomst. Geen database, geen netwerk, geen andere systemen. Daardoor draait een test in milliseconden, en draait de hele set in seconden.

Neem de controle of een webhook echt van Shopify komt. Shopify zet een handtekening bij elk bericht. Klopt die niet, dan moet de app het bericht weigeren. Dat is precies het soort regel dat je één keer goed wilt hebben en daarna nooit meer per ongeluk kapot wilt maken.

// Twee verwachtingen: een echte handtekening klopt, een gewijzigd bericht niet.
it('accepteert een webhook die Shopify ondertekende', function () {
    $body = '{"id":1001}';
    $hmac = base64_encode(hash_hmac('sha256', $body, 'geheim', true));

    expect(ShopifyHmac::valid($body, $hmac, 'geheim'))->toBeTrue();
});

it('weigert een webhook waarvan het bericht is aangepast', function () {
    $hmac = base64_encode(hash_hmac('sha256', '{"id":1001}', 'geheim', true));

    expect(ShopifyHmac::valid('{"id":1002}', $hmac, 'geheim'))->toBeFalse();
});
tests/Unit/ShopifyHmacTest.php

Unit, feature en end-to-end

Unit tests zijn de basis, maar niet het hele verhaal. Je test op drie niveaus:

  • Unit tests controleren één stuk code los. Je hebt er veel van, en ze zijn snel.
  • Feature tests sturen een verzoek door de app heen, zoals een webhook die binnenkomt, en kijken wat er in de database en de wachtrij gebeurt. In Laravel vervang je Shopify en andere systemen daarbij door nepversies, zodat een test nooit een echte winkel raakt.
  • End-to-end tests lopen door de app zoals een gebruiker dat doet, in een echte browser. Ze zijn traag en breekbaar, dus je hebt er weinig van, voor de routes die echt niet mogen falen.

Eerst de test

Waar het kan, schrijven we de test voordat de code bestaat. De test faalt eerst, want de functie is er nog niet. Daarna schrijven we precies genoeg code om hem te laten slagen. Zo weet je dat de test iets controleert, en dat de code doet wat er gevraagd is, niet meer.

Bij een bug werkt het net zo. Eerst een test die de fout laat zien, dan de oplossing. De test blijft daarna in de set staan, zodat dezelfde fout niet terugkomt.

Zo testen wij voor de oplevering

Onze Shopify-apps testen we met Pest, een testframework voor PHP dat op PHPUnit is gebouwd en goed aansluit op Laravel. De werkwijze:

  • Elke wijziging gaat via een pull request, zodat een collega hem kan lezen voordat hij samenkomt met de rest.
  • CI draait de tests bij elke pull request. Een server haalt de code op en draait de volledige set, los van iemands eigen laptop.
  • Rood is stop. Een pull request gaat pas samen met de rest als alle tests slagen.

Deze site werkt op dezelfde manier. Bij elke pull request draaien ruim 80 unit tests, met een controle op codestijl en types. Faalt er één, dan wordt er geen nieuwe versie gebouwd en gaat er niets live.

Wat tests niet doen

Tests controleren wat je hebt opgeschreven. Of dat ook is wat de winkel nodig heeft, zeggen ze niet. Daarom blijft de oplevering een moment waarop de winkelier de app zelf gebruikt, met echte voorbeelden uit de eigen praktijk. Tests zorgen ervoor dat dat gesprek over de juiste dingen gaat, en niet over fouten die we zelf hadden moeten vinden.

Ons standpunt

Een bug die we vinden, krijgt eerst een test. Pas daarna een oplossing.
Brandbrand

Vragen hierover

Wat is een unit test?

Een kleine, automatische controle van één stuk code, zoals een functie, met een vaste invoer en een verwachte uitkomst. Zonder database of netwerk, dus in milliseconden klaar.

Wat is het verschil met een feature test?

Een feature test stuurt een heel verzoek door de app en kijkt naar het resultaat, zoals wat er in de database komt. Een unit test kijkt naar één stuk code los.

Wat is Pest?

Een testframework voor PHP, gebouwd op PHPUnit, met een korte schrijfwijze die goed leest. Laravel ondersteunt het standaard.

Maken tests een app duurder?

Ze kosten tijd bij het bouwen. Die tijd verdienen ze terug bij elke wijziging daarna, omdat je niet alles opnieuw met de hand hoeft te controleren.