Nasazujeme a testujeme (mikro)služby

Pokud dostanete chybu v testu vyšší úrovně, nemáte jenom chybu v testovaném kódu. Také vám někde chybí unit test.
Martin Fowler

Včera jsem se vrátil z GeeConu. Jelikož jsem neuvěřitelně vzdělaný, sečtělý a šikovný, tak mě tam zas tolik věcí nezaujalo. Dostala mě ale prezentace Sama Newmana o testování a nasazování mikroslužeb. Dozvěděl jsem se tam řešení problému, s kterým si nevíme rady.

Máme totiž platformu rozdělenou na služby, které jsou ve správě jednotlivých týmů. To má spoustu výhod. Každý si hraje na svém písečku, může si rozhodnout v jakém jazyce to bude psát, jak to bude testovat, jak často chce nasazovat a spoustu dalších věcí. Tím, že jsme platformu rozdělili na služby, se nám ohromně usnadnila práce. Každý máme svoji hromádku, o kterou se staráme a za kterou neseme zodpovědnost. Od té doby, co jsem se takto rozdělili, jsme se ohromně zlepšili ve vývoji, testování i nasazování jednotlivých služeb. Každá služba má totiž specifické požadavky a najednou nám odpadla většina pokusů najít dokonalé univerzální řešení. Víc experimentujeme a každý tým si najde co mu vyhovuje.

Problém ale je, že jednotlivé služby pořád tvoří jednu platformu a zákazníkovi je jedno, že moje část je skvěle otestovaná a funguje, když platforma nefunguje jako celek. Jednotlivé týmy se starají o svoje služby, ale docela nám drhne nasazování a testování, když se jednotlivé služby dají dohromady. Například je složité rozhodnout jaké kombinace služeb testovat, když má každá služba jiný cyklus nasazování. Dopředu moc nevíte jaké verze jednotlivých služeb se potkají na produkci. Co když neškodná změna jedné služby, rozbije něco ve druhé službě? Existuje několik evidentních řešení, které bohužel nezabírají.

Integrační testy

Ideálním řešením jsou integrační testy, které automaticky otestují celou platformu, všechny případy užití a všechny možné kombinace služeb. Před každou změnou se tyto testy spustí, a když projdou, tak je všechno v pořádku a můžeme nasazovat. Jak už jsem řekl, toto řešení je ideální, což bohužel znamená, že v reálném světě moc nefunguje. Abych mohl spustit integrační testy, tak musím všechno nasadit, pospojovat a pak spustit testy, které klikají skrz UI. To za prvé dost dlouho trvá a za druhé je tam tolik věcí, které se můžou rozbít, že testy někdy víc padají, než procházejí. Co dělat když testy neprojdou? Který tým se na to má podívat? Kdo určí co se rozbilo? Sam Newman sice říkal, že když integrační testy neprocházejí, tak je to dobře. Pravděpodobně to poukazuje na křehkost našeho testování. Ale zestabilnění integračního testování je příliš velké sousto, kterému se sice asi nevyhneme, ale bude to dost dlouho trvat. Do GeeConu jsem věřil, že to jde, že je to jediná správná cesta, že se jen málo snažíme. Ale asi jsem byl příliš velký idealista.

Navíc mi nedošla jedna zásadní věc. Protože jsou integrační testy pracné, pomalé a křehké, je dobré je používat jen na věci, které se jinak otestovat nedají. Třeba na otestování toho, že nám celý systém funguje. Spousta věcí se dá ale otestovat jinak, jednodušeji.

Nasazovat najednou

Druhá možnost je nasazovat najednou. Udělat release okna a dopředu vědět co kdo bude kdy nasazovat. Můžeme se tvářit, že nemáme služby ale jeden celek, který můžeme najednou otestovat a nasadit. V teorii to zní dobře, ale předpokládá to, že nikdo neudělá chybu. Že nikdo na poslední chvíli nezjistí, že mu něco nefunguje nebo že na něco zapomněl. Co pak? Odložit i release ostatních služeb? Následující release pak bude ještě větší, a tím se zvětší i pravděpodobnost, že se něco rozbije. Takže se release zas odloží a tak dál. To je cesta do pekel.

Kontrakt mezi službami

Když se nám nedaří testovat to celé najednou, co takhle dobře otestovat jednotlivé služby a pevně stanovit kontrakt mezi nimi? Něco podobného navrhoval kdysi kolega, že prý nemusíme tak intenzivně testovat celou platformu, stačí když dobře zdokumentujeme API. Tenkrát jsme mu to rozmluvili. Právem. Dokumentace je obvykle neúplná, nepřesná a zastaralá, ta by nám kvalitu nezajistila. Nedošlo nám ale, že jsme byli jen kousíček o řešení. Co takhle přísnější kontrakt? Co když tým spravující jednu službu uzavře kontrakt s týmem spravujícím druhou službu o tom jak ji budou používat. A místo toho, aby si na to plácli, tak na to napíší testy. Říká se tomu Consumer-Driven Contract.

Jednoduše se napíší testy, které říkají, my vás budeme volat takto a vy nám budete odpovídat takto. Když se přihodí toto, tak nám vrátíte tamto. Pokud někdo změní službu, spustí testy, které hlídají její kontrakt. Když projdou, je velká šance, že se nic nerozbilo. Díky testům kontraktu budeme mít otestovanou spolupráci mezi službami, díky ostatním testům služby samotné. Místo velkých megatestů, které to pokryjí najednou od hlavy až k patě, pokryjeme systém menšími, překrývajícími se testy. Navíc budeme mít i zdokumentovaný kontrakt mezi službami, což není nikdy na škodu.

Není to samozřejmě dokonalé, pořád tam budou věci, na které zapomeneme. Pořád budeme potřebovat integrační testy, ale nebude jich potřeba tolik. Stačí jich pár, které ale na oplátku můžeme pouštět třeba i na produkci.


Sam Neward toho radil víc, ale consumer-driven contracts mě zaujaly nejvíce. Abych vás neochudil o zbytek, tady je celá prezentace a toto jsou hlavní rady:

Buďte si vědomi vaší pyramidy testů. Vyvažujte ji.Pyramida testů říká v zásadě to, že byste měli mít hromadu unit testů, spoustu komponentových testů a míň integračních testů. Čím větší daný test je, tím míň jich chcete mít. Je to relativní číslo, neříká, že máte kašlat na integrační testy. Říká jen, že pokud máte víc integračních testů než unit testů, tak je něco špatně.

Rozumějte rovnováze mezi testováním a rychlou nápravou – Máte omezené množství zdrojů, je dobré najít rovnováhu mezi tím, jak umíte testovat a mezi tím jak rychle umíte odhalit a napravit chyby na produkci. Možná je výhodnější mít méně dokonalé testy, ale lepší monitoring a rychlejší releasy. Všimněte si, že je to o rovnováze, rozhodně neříká ať kašlete na testy.

Nasazujte jednu věc po druhé – V daném okamžiku nasazujte jenom jednu službu. Budete mít snazší rollback a diagnostiku chyb.

Zvažte consumer-driven contracts – o tom je celý tento zápisek.

Prozkoumejte nasazovaní založené na imagích, abyste zmenšili rozdíly mezi testovacími prostředími – místo toho, abychom měli jeden image s operačním systémem a všechno ostatní na něj instalovali, je údajně lepší vyrobit image s celou naší službou a ten image pak prohnat celým testovacím potrubím až po produkci. Vyžaduje to mít jednu službu per image, ale díky Dockeru to prý jde udělat lacině.