Cypress and Flaky Tests:Jak zacházet s chybami při vypršení časového limitu

Cypress je automatizovaný komplexní testovací rámec s více než třemi miliony stažení open source týdně v době psaní tohoto článku. Jeho stálá popularita není bezdůvodná; Mezi výhody používání Cypress patří mimo jiné nástroj pro vizualizaci snímků, automatické opětovné načítání po jakékoli změně ve vašich testech a možnost ovládat síťové požadavky a odpovědi, aniž byste kdy zasáhli váš server.

Užil jsem si výhody, které Cypress nabízí po začlenění tohoto nástroje do řady projektů, ale jako každá nová technologie je třeba překonat určité křivky učení. Jedna příležitost k učení, které jsem nedávno čelil, zahrnovala lokálně procházející testovací sadu, která také produkovala neúspěšné testy Cypress na CI. Tento problém mě poslal do králičí nory Stack Overflow, ale od té doby jsem se objevil s nějakou nově nalezenou moudrostí.

Níže naleznete přehled těchto informací, včetně stručného popisu toho, co jsou to šupinaté testy, jak k nim dochází a jak řešit šupinaté testy Cypress, když se objeví lokálně nebo podél vašeho kanálu CI.

Co je to flaky test?

Pojem „špatný test“ je obecný termín, který lze použít pro jakýkoli test napsaný v jakémkoli testovacím rámci. Test je považován za nespolehlivý, když vám poskytuje nekonzistentní výsledky v různých běhech, i když jste v testovacím kódu neprovedli žádné změny. Když spustíte testovací sadu a na začátku úspěšně uspějete, víte, že máte nefunkční test, ale stejný test selže při dalším spuštění (nebo naopak).

Šupinaté testy jsou náhodné, protože důvod jejich nekonzistence není okamžitě zřejmý. Protože se váš testovací kód nezměnil, v zákulisí se musí dít něco jiného a nalezení tohoto problému může být často složité. V závislosti na testovacím rámci, který používáte, však existují některé běžné viníky chybných testů a vytvrzení toho vašeho může vyžadovat jednoduchý proces eliminace.

Pro uživatele Cypress a dalších end-to-end testovacích rámců je váš nespolehlivý test pravděpodobně výsledkem jednoho z následujících:

  • Přítomnost animací ve vašem uživatelském rozhraní
  • Dotyčný nespolehlivý test není dostatečně izolovaný od ostatních testů ve vaší testovací sadě
  • Stav aplikace potřebný k úspěšnému zvládnutí daného testu není před spuštěním testu adekvátně nastaven
  • Asynchronní operace se nedokončí, dokud Cypress nespustí příkaz, což způsobuje chybu časového limitu

Testy na cypřiše způsobené chybným časovým limitem

Jak se ukázalo, selhání mé CI testovací sady Cypress zahrnovalo problém s časovým limitem. Obecně platí, že k "časovému limitu" může dojít, když program neobdrží odpověď během stanovené doby, což má za následek chybu.

V kontextu testování webové aplikace může dojít k chybě časového limitu, když aplikace spustí asynchronní operaci, která musí být dokončena, než bude stav aplikace a/nebo uživatelské rozhraní připraveno k testování. Pokud se příkaz nebo aserce Cypress provede před dokončením této operace, váš test pravděpodobně selže. Pokud však doba potřebná k dokončení této operace kolísá, může být také příležitostně dokončena v dostatečném časovém předstihu na to, aby byl test splněn. Jak si dokážete představit, toto je perfektní recept na vytvoření šupinatého testu.

Jak cypřiš předvídá vločkovité testy

Naštěstí Cypress poskytuje řadu výchozích chování pro předvídání „asynchronní povahy webových aplikací“ a také další možnosti, které mohou vývojáři použít ručně, aby splnili specifické potřeby své aplikace. Jedno takové výchozí chování zahrnuje automatické čekání po dobu čtyř sekund (ideálně, aby aplikace mohla dokončit jakoukoli operaci, kterou může zpracovávat), než dosáhne časového limitu. Vývojáři se mohou rozhodnout toto výchozí nastavení přepsat libovolným počtem deklarací časového limitu, buď v rámci konkrétního testu, sady testů, nebo jako součást vaší globální konfigurace.

cy.get('[data-cy=input-box]', { timeout: 10000 }).type('Input');
cy.get('[data-cy=submit-button]', { timeout: 7000 }).click();
cy.get('[data-cy=input-box]', { timeout: 5000 }).should('not.have.value');

The example above displays three cypress.get() commands with individual timeout specifications for each. Since Cypress sets timeouts in milliseconds, Cypress would wait 10 seconds, 7 seconds, and 5 second before looking for each associated element and executing the subsequent commands and assertions in this example, respectively.

Vývojáři se také mohou rozhodnout povolit opakování testů ve svých globálních konfiguracích. To vyzve Cypress k opakování neúspěšných testů tolikrát, kolikrát vývojář určí.

"requestTimeout": 2000,
"defaultCommandTimeout": 5000,
"retries": 3

The example above displays global configuration options within the cypress.json file. The first two will override Cypress default timeout settings, while the “retries” option specifies how many times Cypress should retry failed tests before moving on to the remainder of the test suite.

A konečně, Cypress také nabízí funkci detekce rozbitého testu na Cypress Dashboard. Pokud je povolena možnost „opakování testu“, tato funkce označí všechny nespolehlivé testy ve vaší testovací sadě a nabídne analýzu počtu a závažnosti těchto testů v průběhu času. Je důležité si uvědomit, že tyto funkce jsou přístupné pouze vývojářům, kteří jsou součástí plánu Cypress Team Dashboard. Při absenci těchto funkcí by vývojáři měli spustit svou testovací sadu několikrát, aniž by provedli změny v kódu, aby zjistili, zda obsahuje nespolehlivé testy.

Způsoby, jak ručně řešit nespolehlivé testy způsobené chybami vypršení časového limitu

Poté, co jsem ve své testovací sadě identifikoval nespolehlivé testy, refaktoroval jsem svou kódovou základnu tak, aby vyhovovala chybám vypršení časového limitu, které způsobovaly nekonzistentní výsledky. Po přenesení změn do vzdálené větve jsem však nyní viděl, jak mé testovací specifikace prošly lokálně, ale selhávaly na CI. Po obnovení základu s hlavní větví a stále jsem viděl neúspěšné testy na CI, začal jsem hledat další řešení, která řeší problémy s časovým limitem testování Cypress.

Následující seznam představuje řadu možností dostupných pro vývojáře, kteří zažívají podobné chyby Cypress, jejichž kombinaci jsem použil k úspěšnému sestavení.

Vyžadovat, aby Cypress čekal na dokončení síťového požadavku

Pokud je váš nespolehlivý test výsledkem toho, že Cypress provádí příkazy a aserce před dokončením nezbytného síťového požadavku, můžete tento požadavek zachytit a požadovat, aby Cypress počkal na jeho dokončení, než spustí další příkazy.

Chcete-li toho dosáhnout, začněte definováním zachycené trasy a přiřazením aliasu. Tento alias lze poté vyvolat později, kdykoli je odpověď na tento požadavek nezbytná pro účely testování. Poté můžete navázat funkci zpětného volání, která provede příkazy Cypress a tvrzení, která jsou součástí vašeho testu.

cy.intercept('GET', '/api/v1/candidate/assessment-attempt*', {
  fixture: 'candidate/stubbedAssessments.json'
}).as('getActiveAssessments');

it('meets default question settings', () => {
  cy.wait('@getActiveAssessments').then(() => {
    cy.get('[data-cy=start-assessment-button]').should('exist');
  });
});

The example above displays an intercepted network request with a specified method and route. This particular interception also stubs the response that this network request would have otherwise provided to our test, instead producing mock data found in the associated fixture file. Lastly, this interception is given an alias, getActiveAssessments, through use of the .as() command. The subsequent test in this code snippet then accesses this alias and requires Cypress to wait on its response before executing anything found in the following callback function.

Zachycení všech síťových požadavků na kontrolu doby odezvy

Odesílání síťových požadavků na váš server za účelem načtení živých dat během procesu testování může mít v některých situacích smysl. Tím se však vaše testovací prostředí otevře několika externím proměnným, které je obtížnější ovládat. Pokud je váš server mimo provoz nebo se liší doba odezvy nebo pokud se vyskytuje více požadavků najednou, můžete ve vaší testovací sadě zaznamenat nespolehlivé testy. Zachycování všech relevantních síťových požadavků v dané specifikaci a poskytování vlastních falešných dat jako odpověď může snížit proměnlivou povahu tohoto síťového provozu. Namísto čekání na odpověď od vašeho serveru může Cypress rychle získat vaše falešná data a pokračovat v testech.

Rozdělte svou testovací sadu na menší specifikace

Dalším způsobem, jak zacházet s chybami vypršení časového limitu, které způsobují nespolehlivé testy, zahrnuje oříznutí velkých souborů specifikací. Dlouhé soubory specifikací jsou nejen náročné na údržbu, mohou také zkomplikovat určení příčiny nekvalitního testu. To platí zejména v případě, že stav aplikace není v rámci testu správně nastaven ani není po dokončení testu vyčištěn, protože tyto faktory mohou ovlivnit následné testy ve vaší testovací sadě a způsobit další selhání. Pokud se jedná o případ více testů v dlouhém souboru specifikací, můžete zjistit, že hrajete hru whack-a-mole, kde úprava jednoho testu vede k selhání druhého.

V souvislosti s chybami vypršení časového limitu mají menší soubory specifikací tu výhodu, že omezují síťový provoz potřebný pro správné dokončení testů. Toto omezení samo o sobě vám může lépe porozumět tomu, co se přesně děje ve vaší aplikaci v době, kdy probíhá váš test, a co musíte ovládat, abyste mohli napsat úspěšný test.

Rozdělení částí souvisejících testů do jejich vlastního nezávislého souboru specifikací zároveň znamená izolaci těchto testů od jakýchkoli zbytečných procesů, které se vyskytovaly ve větší testovací sadě. Spuštění menšího počtu testů a procesů vás může dostat do lepší pozice k nalezení příčiny pochybných testů pomocí procesu eliminace.

Vyžadovat, aby Cypress počkal libovolný počet sekund

Poslední možnost v tomto seznamu zahrnuje použití cy.wait() k ručnímu určení, kolik sekund má Cypress čekat v daném bodě vašeho testovacího souboru. Toto řešení je jednoduché, ale ne zcela spolehlivé, takže byste ho měli považovat za jakousi poslední možnost nebo rychlé řešení; i když můžete být schopni pozastavit Cypress na dostatečně dlouhou dobu, abyste se vyhnuli chybě vypršení časového limitu, tento výsledek není vždy zaručen, zvláště pokud se vaše aplikace později rozroste a zavede nové funkce a chování. Zároveň může být implementace libovolného příkazu čekání také zcela zbytečná. Můžete se například nechtěně pozastavit a čekat na dokončení operace, která již byla dokončena.

cy.wait(10000);

The above command requires Cypress to wait 10 seconds before moving on to the subsequent code in a spec file.

Pomocí cy.wait() zadat libovolný počet sekund, po které má Cypress čekat, může být v některých kontextech stále užitečné. Pokud je vaše aplikace relativně malá nebo pokud jsou vaše soubory specifikací a testy dostatečně izolované, může být riziko implementace zbytečného nebo nespolehlivého příkazu čekání dostatečně malé, aby ospravedlnilo jejich použití. Než se však obrátíte na toto řešení, možná budete chtít vyčerpat další alternativy, protože příliš mnoho z těchto příkazů může zatěžovat dobu běhu testovací sady a může ve skutečnosti poukazovat na hlubší problém související s vaší testovací sadou nebo webovou aplikací.

Klíčové poznatky

  • Nefunkční test je jakýkoli test, který poskytuje nekonzistentní výsledky, přestože mezi testovacími běhy neprobíhají žádné změny v testovacím kódu.

  • Někdy jsou nepravidelné testy Cypress výsledkem chyb časového limitu; asynchronní proces v kódu vaší aplikace může být dokončen před nebo poté, co Cypress otestuje dané tvrzení, což vede k nekonzistentním výsledkům.

  • Cypress nabízí některá výchozí nastavení časového limitu pro předvídání asynchronních procesů v kódu vaší aplikace. Pokud tato výchozí ochranná opatření selžou, vývojáři se mohou rozhodnout je přepsat v rámci svého testovacího kódu nebo v rámci svých globálních konfigurací.

  • Vývojáři se mohou rozhodnout ručně řešit nespolehlivé testy způsobené chybami vypršení časového limitu provedením jednoho z následujících nebo jejich kombinací:

    • Použijte příkazy Cypress intercept a aliasing, abyste požadovali, aby Cypress počkal na dokončení vašich asynchronních operací před spuštěním dalšího příkazu nebo aserce.
    • Pomocí příkazu Cypress intercept ovládejte veškerý síťový provoz nezbytný pro vaše testy, abyste odstranili nekonzistence mezi testovacími běhy.
    • Rozdělte svou testovací sadu na menší specifikace, abyste omezili počet asynchronních operací, na které se vaše testy spoléhají, a pomozte rychleji najít příčinu nespolehlivých testů.
    • Použijte cy.wait() příkaz k ručnímu požadavku na Cypress, aby počkal zadaný počet sekund před spuštěním daného testu.

Závěr

Všechny způsoby, jak se vypořádat s potrhaným cypřišovým testem, by pravděpodobně zaplnily velmi tlustou knihu. Doufejme, že některé ze zde uvedených možností vám pomohou vyřešit váš problém nebo vás nasměrují správným směrem.

Tento článek byl poprvé publikován na shipshape.io.