End-to-End testomgevingen, een “dead End road”

End-to-End testomgevingen: Omgevingen die zijn opgezet om de integratie te testen. Maar met de opkomst van ‘Distributed Architecture’, onafhankelijke DevOps teams en geautomatiseerde CI/CD staat de End-to-End testomgeving onder druk. Ze zijn instabiel, niet te vertrouwen en moeilijk te onderhouden. Waarom is het gebruik van End-to-End testomgevingen een doodlopend pad en wat zijn alternatieven?

Auteur: Roy Braam

Zoals je misschien al had verwacht, ben ik niet zo’n fan van End-to-End testomgevingen. In de afgelopen jaren heb ik een haat-liefderelatie opgebouwd met deze omgevingen. De discussies en gesprekken er over waren in het begin frustrerend, maar gaandeweg ben ik het leuk gaan vinden om er met andere over te sparren. Persoonlijk zie ik de waarde van deze omgevingen niet, zeker niet als je dat afsteekt tegen alle energie die erin wordt gestopt. Ik wil niet meteen beweren dat End-to-End testomgevingen nooit nodig zijn, maar ik ben nog geen probleem tegen gekomen waarbij een End-to-End testomgeving de beste oplossing is.

Definitie

Met een End-to-End testomgeving bedoelen we een omgeving waarin teams hun deliverables deployen om die vervolgens te kunnen testen tegen de deliverables van andere teams, met als doel: geïntegreerd testen. Het is dus geen omgeving die een team opzet om de integratie van al hun eigen software te testen, of een front-end die getest wordt in samenwerking met een backend. We hebben het hier over omgevingen die speciaal bedoeld zijn om de integratie te testen tussen verschillende teams. Geen “Test Doubles” (Stubs, Mocks, etc.)(1), maar software die ook in productie zal gaan draaien.

Een End-to-End testomgeving gaat schuil onder verschillende namen zoals:

– Chain environment
– User Acceptance Environment
– Broad-stack Environment
– Theory (In Theory it works, in production it doesn’t)
– Acceptance Environment
– Pre-Production Environment
– Enterprise-wide Integration Test Environment
– Full-stack Environment
– Production-like Environment

Nadelen

Er zijn een aantal aspecten van End-to-End testomgevingen die, om het netjes te verwoorden, niet ideaal zijn.

Test-Data

Om te kunnen testen heb je data nodig. Aangezien we willen testen tegen systemen van andere teams, hebben die systemen ook data nodig. We moeten er dus voor zorgen dat de test-data afgestemd is. Wat gebeurt er als data geconsumeerd wordt, hoe zorg je er voor dat bij een volgende testronde de begin situatie weer gelijk is? Of wat gebeurt er als een ander team de data consumeert waarop testen gebaseerd zijn? Kortom, je bent afhankelijk van de data die andere in hun systeem hebben zitten.

Production-like

De semantiek van de data en de software die er gedeployed is, is gelijk aan die van productie. Tenminste, dat zou zo moeten zijn als het ‘Production-like’ is. Voor wat betreft de semantiek van de data lijkt me dat haalbaar. In ieder geval zal die beter zijn dan een Test Double (Stub, Mock etc.), maar de versie van de deployments zullen nog wel eens verschillen met die van productie. Zeker in een (microservice) architectuur waarbij meerdere onafhankelijke teams samenwerken aan losse services die communiceren met elkaar. Teams deployen constant nieuwe versies in de End-to-End testomgevingen om die vervolgens te testen. Totdat ze die versie ook in productie deployen hebben ze de End-to-End test minder op productie laten lijken voor alle andere. Des te meer teams, des te groter het probleem. Iedereen doet zijn uiterste best om de omgeving zo min mogelijk op productie te laten lijken.

Instabiele testen

End-to-End test omgevingen ‘leven’, ze blijven bestaan en veranderen constant. Data wordt gemuteerd, verwijderd en toegevoegd waardoor voor een volgende test de uitgangspositie anders is. De omgeving heeft een ‘state’ en die verandert constant. Dit in tegenstelling tot Unit testen en Integratie testen die, als het goed is, telkens dezelfde uitgangspositie hebben. Door de veranderingen in de omgeving worden de testen die daarop worden uitgevoerd instabiel.

Lange feedback tijd

Als je aan het bouwen bent aan een applicatie, wil je snel weten of dat wat je bouwt goed is. Als je om te testen eerst alles moet deployen in een omgeving, vergroot dat de feedback tijd. Als het mogelijk is om hetzelfde te testen in een lager deel van de test-piramide, bijvoorbeeld Unit testen, dan zal dat je sneller feedback geven.

Langere ‘time to market’

Dit argument werkt altijd goed bij Product Owners. Instabiele testen, langere feedback tijd en test afhankelijkheden met andere teams zorgen voor langere tijd tussen het idee en het gebruik van de oplossing.

Kosten

Een End-to-End test omgeving kost geld. Misschien goed om nog even te vermelden: de virtuele machines waar alles binnen draait, draaien niet op virtueel ijzer. Denk aan kosten voor ijzer, Cloud, licentie en onderhoud. Daarnaast dragen alle boven genoemde punten ook bij aan hogere kosten.

Schijnvoordelen

Er lijken wat voordelen te zijn bij het gebruik van End-to-End testomgevingen. Deze zijn veelal onder te verdelen in de volgende onderwerpen:

Kwaliteit van Test data

In het algemeen lijkt de testdata in een End-to-End testomgeving meer op die in productie dan die uit Test Doubles (1). Denk aan de semantische correctheid van data zoals een waarde in een veld die je terug krijgt en niet had voorzien in je Enum. Dit wil nog niet zeggen dat je dit het beste kan testen in een End-to-End testomgeving. Beter zou zijn om:
– Strikte en duidelijke specificaties en contracten tussen systemen te maken.
– Logica die afhankelijk is van output van andere systemen, testen in Unit testen.
– Als er veel waarde gehecht wordt aan de data in een End-to-End test omgeving, deze extraheren naar een Stub.
– “Build for failure”: je gaat hoe dan ook onverwachte antwoorden krijgen van andere systemen. Zorg er voor dat dit op een gebruikersvriendelijke manier wordt afgehandeld in plaats van een HTTP 500 met een stacktrace.

Vertrouwen

“Ik wil het zien werken voordat we naar productie gaan” is een veelgehoord argument. Er doorheen klikken voordat het naar productie gaat en het zien werken. Eigenlijk is dit een indicatie dat er geen vertrouwen is in de automatische testen. Of, dat er geen automatische integratie testen zijn en er een liefde is voor handmatig testen. Wat als er een handmatige test vergeten wordt?

– Bouw daarom vertrouwen op door automatisch te testen.
– Bij een bug, maak eerst een falende test en los die dan op zodat de bug niet meer terug kan komen.
– Investeer in het automatiseren.
– Laat zien dat er geen nieuwe problemen gevonden worden in de End-to-End testomgeving en deze dus niks bijdraagt.

Volwassenheid

De antwoorden die de gebruikte services geven, zijn nog steeds conform het contract, maar toch gaat er iets mis en zit je met een productie incident. Met een beetje geluk had dit voorkomen kunnen worden door een handmatige test in de End-to-End testomgeving, maar dat is symptoombestrijding. Pak de oorzaak van het probleem aan.

– Strikte en duidelijke contracten zodat er minder interpretatie verschillen zijn.
– Wees klaar voor het onverwachte en zorg er voor dat de gebruiker zo min mogelijk merkt van onverwacht gedrag.
– Pas Consumer Driven Contract testen toe om de verwachtingen en afspraken te testen. (Zie later in dit artikel)

Compensatie

Niet elk team is in staat (wat de reden dan ook mag zijn) om de kwaliteit te leveren die ze zouden willen leveren. Of dit problemen oplevert voor jouw team kan je verifiëren in een End-to-End testomgeving voordat ze een nieuwe versie naar productie brengen. Hiermee houd je het probleem alleen maar in leven, de kwaliteit wordt gecompenseerd met extra testen in een End-to-End test omgeving door een ander team. Beter zou zijn om:

– De verantwoordelijkheid te laten waar het hoort en het daar oplossen.
– Er voor zorgen dat je berekend bent op fouten en zorg dat deze netjes worden afgehandeld, een HTTP 500 met stacktrace is dat niet.

Mocht je toch een verificatie willen doen voordat ze naar productie gaan, zorg er dan voor dat je geautomatiseerde de verwachtingen test. Hiermee zorg je er voor dat wat je verwacht terug te krijgen in ieder geval klopt, zonder dat je dit in een End-to-End test omgeving moet testen.

Tips

CDC

Consumer Driven Contract testen (2) zijn, zoals de naam al zegt, afnemer gestuurde contract testen. Het idee is simpel, bij het afnemen van een service van een ander team heb je altijd een producerend team en één of meerdere consumerend team. Traditioneel maakt het producerende team een specificatie voor hun service die het consumerende team kan afnemen. CDC voorziet in het testen van het contract tussen services door in isolatie de verwachtingen van zowel het request als het response te testen, zodat er overeenstemming is over het gedocumenteerde contract. Praktisch houdt dit in dat het consumerende team de verwachtingen die ze hebben kunnen aanbieden in de vorm van testen aan het producerende team. Het producerende team kan vervolgens die testen meenemen bij het bouwen van het project. Dit heeft een aantal voordelen:

– Het consumerende team kan waarborgen dat de service volgens het contract blijft werken.
– Het producerende team kan testen of ze bij aanpassingen de verwachtingen van andere teams breken.
– De verwachtingen kunnen worden gebruikt als Stub door het consumerende team zodat ze die kunnen testen tegen hun eigen applicatie, in isolatie.

Maar CDC heeft nog meer voordelen: het kan gebruikt worden voor het ontwerp van specificaties. Als het consumerende team precies weet wat ze nodig hebben, waarom zouden ze dan niet de specificatie in de vorm van een test aanbieden aan het producerende team. Het producerende team hoeft dan alleen maar de implementatie te schrijven die voldoet aan de verwachtingen. Of, het producerende team kan alle endpoints opruimen waarvoor geen testen zijn, want blijkbaar verwacht niemand daar nog een antwoord op. Projecten die je bij CDC kunnen helpen zijn Spring Cloud Contract, Pact maar ook Postman/Newman.

Verklein de Risico’s

Nadat je alles uitvoerig getest hebt, zonder End-to-End test omgeving natuurlijk :), is het tijd om met die nieuwe versie naar productie te gaan. Hoe goed je ook test er kan altijd wat mis gaan. Dat het mis gaat kan je niet altijd verhelpen maar, hoe erg het mis gaat wel! Er is geen voldoening te behalen uit die heldendaden die je verricht in crisisteams omdat het hele systeem op zijn gat ligt door een release waar jij betrokken bij bent. Echte helden voorkomen dat het zo ver komt.

Ontkoppelen van deploy en release

Met het deployen bedoelen we het plaatsen van een nieuwe versie en met releasen het beschikbaar stellen van de nieuwe functionaliteit. Eerst deploy je dus de nieuwste versie in productie, maar is de nieuwe functionaliteit nog niet geactiveerd. Eerst kijk je hoe de nieuwe versie zich gedraagt, als alles goed gaat activeer je de nieuwe functionaliteit. Bijvoorbeeld door een ‘feature toggle’.

Release snel en vaak

Het risico van een kleine aanpassing is kleiner dan heel veel kleine aanpassingen in één keer. Als het release proces pijn doet, dan moet je het vaker doen en er voor zorgen dat het geen pijn meer doet.

Canary release

Stuur niet gelijk al het verkeer naar de nieuwe versie, maar kijk eerst hoe de nieuwe versie zich gedraagt bij een kleiner deel van het verkeer voordat je het uitrolt naar alle gebruikers. (3)

Monitoring

Om te weten wat er in productie gebeurt, moet je goed monitoren. Je wil onder andere weten:

– Hoeveel fouten er optreden
– Hoeveel requests binnenkomen
– De response codes en de aantallen

Op de belangrijkste statistieken wil je waarschijnlijk ook push notificaties hebben naar mail/chat/telefoon als een bepaalde drempel wordt bereikt.

Blue/Green deployments

Bij Blue/Green deployments (4) deploy je een nieuwe release (groen) naast je huidig productie release (blauw). Vervolgens zorg je er voor dat bijvoorbeeld 5% van de requests naar de nieuwe release gaan. Dit monitor je en als je tevreden bent verhoog je het aantal request naar de nieuwe release net zolang tot dat alles naar de nieuwe versie gaat. De oude release kan dan weg en groen wordt de volgende blauw. Dit heeft als voordeel dat je snel terug kan en dat de impact van problemen klein gehouden wordt door niet gelijk 100% naar de nieuwe versie te sturen.

Conclusie

Het stoppen met End-to-End testomgevingen is zeker niet altijd eenvoudig. Het gaat gepaard met vallen en opstaan, maar uiteindelijk ga je wel een stuk sneller. Elk stapje weg bij End-to-End testomgevingen is een feestje waard.

Referenties

  1. https://martinfowler.com/bliki/TestDouble.html
  2. https://reflectoring.io/7-reasons-for-consumer-driven-contracts/
  3. https://blog.getambassador.io/cloud-native-patterns-canary-release-1cb8f82d371a
  4. https://semaphoreci.com/blog/blue-green-deployment