De Illusie van Veiligheid

We kennen het allemaal wel. Dat bevredigende moment wanneer je al die groene vinkjes ziet in je CI/CD-dashboard. Het voelt als een schouderklopje van de programmeergoden zelf. Maar hier is de adder onder het gras: die tests kunnen je een vals gevoel van veiligheid geven.

Waarom? Laten we het eens nader bekijken:

  • Onvolledige testdekking
  • Fluctuerende tests die soms slagen...
  • Tests die niet echt controleren wat je denkt dat ze doen
  • Verschillen tussen test- en productieomgevingen

Elk van deze factoren kan bijdragen aan wat ik graag de "Groene Leugen" noem - wanneer je tests slagen, maar je code nog steeds zo stabiel is als een kaartenhuis in een storm.

Het Dekking Dilemma

Laten we het hebben over testdekking. Het is een maatstaf die in ontwikkelteams rondgaat als een hete aardappel. "We hebben 80% dekking!" roepen ze trots. Maar hier is een gedachte om over na te denken: 100% testdekking betekent niet dat 100% van het gedrag van je code is getest.

Overweeg deze bedrieglijk eenvoudige JavaScript-functie:


function divide(a, b) {
  return a / b;
}

Je zou een test als deze kunnen schrijven:


test('deel 4 door 2 is gelijk aan 2', () => {
  expect(divide(4, 2)).toBe(2);
});

Gefeliciteerd! Je hebt 100% dekking. Maar wat als je deelt door nul? Wat als je te maken hebt met floating-point precisie? Wat als je met grote getallen werkt? Je test liegt tegen je en geeft je een vals gevoel van veiligheid.

Het Fluctuerende Test Fiasco

Ah, fluctuerende tests. De vloek van elke ontwikkelaar. Dit zijn de tests die 9 van de 10 keer slagen, je in een vals gevoel van veiligheid wiegen, om vervolgens spectaculair te falen wanneer je het het minst verwacht.

Fluctuerende tests zijn vaak het resultaat van:

  • Racecondities
  • Tijdafhankelijke logica
  • Externe afhankelijkheden
  • Beperkingen van middelen

Hier is een klassiek voorbeeld van een potentieel fluctuerende test:


test('gebruiker is aangemaakt', async () => {
  await createUser();
  const users = await getUsers();
  expect(users.length).toBe(1);
});

Ziet er onschuldig uit, toch? Maar wat als getUsers() wordt aangeroepen voordat de database klaar is met het aanmaken van de gebruiker? Dan heb je een fluctuerende test die meestal slaagt, maar net vaak genoeg faalt om je gek te maken.

De Veronderstelling van Asserties

Soms ligt het probleem niet bij wat we testen, maar hoe we het testen. Overweeg deze Python-test:


def test_user_registration():
    user = register_user("[email protected]", "password123")
    assert user is not None

Deze test slaagt zolang register_user iets teruggeeft dat niet None is. Maar betekent dat echt dat de gebruiker succesvol is geregistreerd? Wat als de functie altijd een lege dict teruggeeft bij een fout? Onze test geeft ons een duim omhoog, maar de werkelijkheid kan heel anders zijn.

Het Omgevingsraadsel

Hier is een leuk feitje: je testomgeving en je productieomgeving zijn ongeveer net zo vergelijkbaar als een speeltuin en een oorlogsgebied. Zeker, ze zien er aan de oppervlakte hetzelfde uit, maar de dynamiek is compleet anders.

Dingen die kunnen verschillen tussen test en productie:

  • Datavolume en -variëteit
  • Netwerkvertraging en betrouwbaarheid
  • Gelijktijdige gebruikers en belasting
  • Gedrag van externe diensten

Je tests kunnen met vlag en wimpel slagen in een ongerepte testomgeving, om vervolgens spectaculair te crashen en te branden wanneer ze worden geconfronteerd met de harde realiteit van productie.

Dus, Wat Kunnen We Doen?

Voordat je je handen in wanhoop in de lucht gooit en alle testen als zinloos verklaart, haal diep adem. Er zijn manieren om de liegende tests te bestrijden en betrouwbaardere CI/CD-pijplijnen te bouwen:

  1. Verbeter de kwaliteit van testdekking, niet alleen de kwantiteit: Streef niet alleen naar een hoog dekkingspercentage. Zorg ervoor dat je tests daadwerkelijk zinvolle scenario's testen.
  2. Implementeer chaos engineering: Introduceer opzettelijk fouten en randgevallen in je testomgevingen om verborgen problemen te ontdekken.
  3. Gebruik property-based testing: In plaats van testgevallen hard te coderen, genereer je een breed scala aan invoer om randgevallen te vangen waar je misschien niet aan hebt gedacht.
  4. Monitor testbetrouwbaarheid: Houd fluctuerende tests bij en geef prioriteit aan het oplossen ervan. Tools zoals Flaky kunnen helpen bij het identificeren van inconsistente tests.
  5. Simuleer productie-achtige omstandigheden: Gebruik tools zoals LocalStack om realistischere testomgevingen te creëren.

De Conclusie

Onthoud, een groene CI-pijplijn is geen garantie voor foutloze code. Het is een startpunt, niet de eindstreep. Benader je tests altijd met een gezonde dosis scepsis en de bereidheid om dieper te graven.

Zoals het gezegde luidt: "Vertrouw, maar verifieer." In de wereld van CI/CD moeten we dat misschien aanpassen naar "Vertrouw je tests, maar verifieer alsof je productie ervan afhangt." Want dat doet het ook.

"De gevaarlijkste soort test is degene die je een vals gevoel van vertrouwen geeft." - Anonieme ontwikkelaar die te vaak is verbrand

Dus de volgende keer dat je die bevredigende zee van groen ziet in je CI-dashboard, neem een moment om jezelf af te vragen: "Vertellen mijn tests me de waarheid, de hele waarheid en niets dan de waarheid?" Je toekomstige zelf (en je gebruikers) zullen je er dankbaar voor zijn.

Stof tot Nadenken

Voordat je gaat, hier is iets om over na te denken: Hoeveel tijd besteed je aan het schrijven van tests versus het analyseren en verbeteren van je bestaande tests? Als je zoals de meeste ontwikkelaars bent, is het antwoord waarschijnlijk "niet genoeg." Misschien is het tijd om dat te veranderen?

Onthoud, in de wereld van softwareontwikkeling kan een beetje paranoia een lange weg gaan. Veel succes met testen, en moge je productiedeployments altijd in je voordeel zijn!