18. Nov 2022Frontend

Ako na end-to-end testovanie pomocou aplikácie Cypress?

Po dlhšej dobe by som vašu pozornosť rád upriamil na zaujímavý nástroj v oblasti testovania. Ako už z názvu vyplýva, budeme sa venovať E2E testom za využitia cypress nástroja. Prečo sme sa tomu venovali?

Roman HaluškaFrontend developer

V rámci našej firmy sme sa už dlhodobo zamýšľali ako vylepšiť interné procesy, zvýšiť efektivitu v prípade väčších zmien v aplikácii, a zároveň priniesť pridanú hodnotu pre našich zákazníkov v oblasti automatizovaného testovania. V článku sa budeme venovať základným informáciám o E2E testoch a následnému praktickému využitiu cypressu.

Integračné testy

Integračné testovanie je technika, pri ktorej sú jednotlivé softvérové moduly testované ako skupina. Aplikácie sú tvorené z rôznych modulov, napríklad v prípade reactu komponentami, ktoré môžu implementovať aj rôzni členovia softvérového tímu, čo vedie aj k vyššej chybovosti kódu. Účelom úrovne testovania je odhaliť chyby v interakcii medzi týmito modulmi. Zameriava sa na kontrolu dátovej komunikácie a integrity medzi komponentami.

End to End testy

E2E testing je typ testovania, ktorým otestujte vašu web aplikáciu pomocou webového prehliadača až po samotný back-end, taktiež môžete pretestovať aj integrácie s API službami tretích strán. Tento druh testov je skvelý na zabezpečenie fungovania vašej aplikácie ako celku. Zároveň je možné tieto testy využiť aj ako integračné. Ako môžeme vidieť na obrázku nižšie, s týmito testami stúpa komplexita, časová náročnosť a klesá aj rýchlosť. Na druhej strane sú určite podstatne efektívnejšie ako manuálne testovanie. Po počiatočných útrapách a nastaveniach testovacieho prostredia sa všetky vyššie spomenuté nevýhody minimalizujú, ak nedochádza k väčším zmenám počas vývoja. Odporúčanie je testy písať až na konci vývoja aplikácie, ak sú počas tohto procesu očakávané veľké zmeny, ktoré by ovplyvňovali v značnej miere už napísané testy, a tým by predlžovali čas uzavretia testovania a nasadenie aplikácie do produkcie.

Zdroj: medium.com

Na začiatku je náročnejšie nastaviť a pripraviť celú infraštruktúru tak, aby všetko fungovalo správne. Je potrebné dbať na zvolenú stratégiu a kvalitu samotných testov. V prípade zložitých testovacích scenárov odporúčam dobre si rozmyslieť štruktúru a organizáciu. Ak už máte všetky tieto úvodné nastavenia za sebou, tak výhodou je, že aplikáciu máte pretestovanú automaticky, ak ste zvolili integráciu do vašej CI/CD infraštruktúry, a to aj z pohľadu UX.

Príklady scenárov:

  1. Validácia kritických obrazoviek aplikácie, ako prihlásenie a registrácia.
  2. Kontrola dát tak, aby boli perzistentné a zobrazovali sa konzistentne naprieč viacerými stránkami vašej aplikácie.
  3. Automatizácia testov v rámci pipelines a následná kontrola pred nasadením aplikácie.

🧪 Cypress

Cypress je testovací nástroj novej generácie pre moderné front-endové aplikácie. Venuje sa primárne kľúčovým problémom, ktorým čelia vývojári a inžinieri QA pri testovaní aplikácií. Umožňuje písanie, spúšťanie, správu a ladenie unit, integračných a e2e testov.

Prečo sme si zvolili cypress?

Ako prvý nástroj sme zvolili selenium, s ktorým je cypress často porovnávaný. Mali sme v pláne vyskúšať aj iný nástroj, aby sme vedeli porovnať všetky výhody a nevýhody. Preto sme siahli aj po cypresse. Výsledkom bolo, že cypress poskytuje viac možností, a zároveň má menej limitov ako selenium. Cypress je aj architektonicky odlišný od selenia, a preto vám umožňuje písať rýchlejšie, jednoduchšie a spoľahlivejšie testy.

Výrok na oficiálnej stránke: "Až doteraz nebolo komplexné testovanie jednoduché. Bola to časť, ktorú vývojári nenávideli. Cypress uľahčuje nastavenie, písanie, spúšťanie a ladenie testov." môžem teda len potvrdiť. Cypress odporúčam, uľahčuje testovanie a pracuje sa s ním veľmi dobre. 🙂

🔧 Inštalácia a nastavenia

Informácie o základných nastaveniach a inštalácii nájdete tu.

Stratégie testovania

Pred každým testom je potrebné mať testovacie dáta uložené napríklad v databáze. Na pripravenie týchto dát pred testom je veľa postupov. V tomto článku si ďalej ukážeme základné informácie o dvoch z nich, a to "Seeding data" a "Stubbing server".

Seeding data

Klasický spôsob pri e2e. Dynamické, pripadne statické generovanie dát a asociácií. V cypresse sa všetky statické testovacie dáta nachádzajú v zložke fixtures v JSON formáte. Tieto dáta si môžete logicky rozdeliť do jednotlivých súborov na základe kontextu k testom. Pomocou týchto dát si viete vytvoriť napríklad entitu používateľa a odoslať POST request na váš server, ktorý ho spracuje a uloží do databázy. Následne viete dopytovať stránku, závislú napríklad od identifikátora.

cy.visit(`/cars/${carID}`)

Na napĺňanie týchto dát sú dostupné tri funkcie:

  1. cy.exec() - na spustenie systémových príkazov
  2. cy.task() - na spustenie kódu v Node cez funkciu setupNodeEvents
  3. cy.request() - na vytváranie serverových HTTP požiadaviek

Najjednoduchší spôsob je využívať funkciu cy.request(), ktorou si vytvoríte požiadavku s testovacími dátami a odošlete ju na server. Viď príklad nižšie…

describe('Update car', () =>{
  cy.request ('POST', '/test-api/cars/${carID}', {
    brand: Volvo
    model: V60
    yearOfProduction: 2020
  })
})

Tento prístup zvyšuje komplexnosť. Budete bojovať so synchronizáciou stavu medzi vašim serverom a frontend aplikáciou. Tento stav budete musieť vždy pred testami nastaviť, prípadne vymazať, čo spomaľuje samotné vykonávanie testov, preto na to treba myslieť a nezabúdať na to.

Stubbing server - alias ignorovanie odpovedí zo serveru

Ďalším platným prístupom, ktorý je protikladom k "seeding data". Jedná sa o prístup, kde nahradíte odpoveď zo servera vašimi vygenerovanými dátami, a tak ignorujete dáta, ktoré ste dostali zo servera, čím úplne obchádzate váš backend server. To znamená, že namiesto resetovania alebo naplnenia databázy do požadovaného stavu môžete prinútiť server, aby odpovedal čímkoľvek, čím chcete. Týmto spôsobom nielenže zabránime nutnosti synchronizovať stav medzi serverom a frontendom, ale tiež zabránime mutovaniu stavu medzi jednotlivými testami. To znamená, že testy nevytvoria stav, ktorý by mohol ovplyvniť iné testy. Jednou z ďalších výhod je, že vám to umožňuje zostaviť vašu aplikáciu bez toho, aby ste potrebovali mať zapnutý server, čo využijete skôr pri integračných testoch. Aj keď je stubovanie rýchlejšia technika, má aj svoje nevýhody, napríklad, že nemáte záruky, či vami "podstrčené" dáta zodpovedajú tomu, čo server reálne odošle. Ďalšou možnosťou v rámci tejto techniky je vygenerovať si testovacie dáta vopred na serveri pre každý test, a následne prijímať tieto dáta, kde predchádzate problému, ktorý je popísaný vyššie.

Vyváženejším prístupom je integrácia oboch stratégií. V oficiálnej dokumentácii odporúčajú použiť na hlavný scenár techniku "seeding data". Akonáhle zistíte, že tento test funguje, môžete využiť "stubbing" techniku na testovanie všetkých okrajových prípadov a ďalších scenárov. Používanie reálnych údajov v drvivej väčšine prípadov neprináša žiadne výhody. Odporúčanie je, aby prevažná väčšina testov používala statické dáta. Vykonávanie testov bude rádovo rýchlejšie a oveľa menej zložité.

Best practices

1. 🗃️ Organizácia testov, 🔐 Prihlásenie/Autorizácia, 🎛️ Controlling state

❌ Zdieľanie objektov medzi jednotlivými stránkami/testami, používanie používateľského rozhrania na prihlásenie.
✅Otestujte jednotlivé stránky/elementy v izolácii, použite API službu na prihlásenie do svojej aplikácie. Kontrolujte jednotlivé stavy a dáta iba v rámci daného testu.

Do pozornosti dávam krátke video .

2.  Výber elementov

❌ Používanie nešpecifických "značiek" pre výber jednotlivých elementov, ktoré môžu podliehať zmenám, napríklad v prípade aktualizácii UI knižnice.
✅ Pomocou atribútu data-* poskytnite kontext svojim selektorom a izolujte ich od zmien.

V oficiálnej cypress dokumentácii uvádzajú, že tieto selektory data-cy, data-test, data-testid sú štandardom v prípade unikátneho identifikátora elementu.

<button
  id="main"
  calss="btn btn-large"
  name="submission"
  role="button"
  data-cy="submit"
>
  Submit
</button>

Vyššie je uvedený príklad button elementu. Jednotlivé prípady cypress príkazov pre vybratie button elementu môžete vidieť v tabuľke nižšie.

3. 🏢 Testovať externé služby tretích strán

❌ V rámci testu sa snažiť "navštíviť" a interagovať s aplikáciou tretích strán, ktorú nemáte pod vlastnou kontrolou.
✅ Testujte len to, čo máte pod kontrolou. Pokúste sa vyhnúť testovaniu služieb tretích strán. V prípade potreby vždy použite cy.request() na komunikáciu so servermi tretích strán prostredníctvom ich rozhraní API.

Ak bude potrebné získať prístup k serverom tretích strán v rámci testov, napríklad v týchto prípadoch:

  1. Testovanie prihlásenia/registrácie, keď vaša aplikácia používa na autorizáciu služby tretích strán
  2. Overenie, či sa napríklad aktualizovali dáta, ktoré odoslala vaša aplikácia na server tretích strán
  3. Na kontrolu odosielania emailových notifikácii, pre ktoré využívate služby tretích strán

tak, ako už bolo vyššie spomenuté, vždy na tieto účely používajte cypress funkciu cy.request().

📧  Overenie odoslaných emailov/notifikácií

Ak vaša aplikácia odosiela e-maily priamo cez váš server SMTP, môžete použiť dočasný
lokálny testovací SMTP server spustený v Cypresse. Podrobnosti nájdete v blogovom príspevku „Testovanie e-mailov HTML pomocou Cypressu".

V tomto prípade je potrebné pridať nastavenie SMTP servera do zložky "plugins" súbor index. Príklad nastavenia nižšie.

const ms = require('smtp-tester')

/**
 * @type {Cypress.PluginConfig}
 */
export default (on: any, config: any) => {
	// starts the SMTP server at localhost:7777
	const port = 7777
	const mailServer = ms.init(port)
	console.log('mail server at port %d', port)

	// process all emails
	mailServer.bind((addr, id, email) => {
		console.log(addr, id, email)
	})
...

Ak vaša aplikácia používa e-mailovú službu tretej strany, môžete použiť skúšobnú e-mailovú schránku
s API prístupom. Podrobnosti nájdete v blogovom príspevku „Úplné testovanie e-mailov HTML pomocou účtov SendGrid a Ethereal Accounts".

Automatizácia a integrácia do bitbucket pipelines

Pre integráciu a automatické spúšťanie e2e testov sme zvolili postup spustenia celej infraštruktúry projektu BE, FE a všetkých potrebných služieb, ako napríklad databázy pomocou docker compose nástroja. Jedným príkazom docker-compose up -d sa na pozadí spustí build a konfigurácia celej infraštruktúry aplikácie. Všetky inštrukcie pre tento proces sa nachádzajú v súbore docker-compose.yml. Bližšie informácie o nastaveniach a možnostiach docker compose nájdete :docker: tu. Následne sú nad touto inštanciou spúšťané testovania pomocou nástroja cypress. Tieto testy môžete spúšťať v časových intervaloch. My ich spúšťame raz denne pre development branch-u. Príklad výpisu výsledku testov v bitbucket konzole môžete vidieť nižšie.

Ak vám testy odhalia chybu alebo problém, tak následné debuggovanie a ladenie vám uľahčia videá, prípadne snímky obrazovky, ktoré si môžete stiahnuť a pozrieť v zložke "Artifacts", viď. obrázok nižšie. Je potrebné ale nastaviť toto ukladanie súborov do vášho docker-compose.yml súboru. Takto si môžete uložiť aj výpisy z vašej BE služby. K uloženým súborom máte v našom prípade prístup 14 dní po vykonaní pipelin-y.

...
artifacts: # store cypress images, videos and BE logs
  - cypress/screenshots/**
  - cypress/videos/**
  - backend/logs/**
...

Praktická ukážka

Príklad testovacej súpravy pre registráciu a autorizáciu používateľov do aplikácie. V rámci testov využívame vlastné selektory, v ktorých je implementovaná potrebná logika na výber komponentu a ďalšie potrebné operácie. Tieto selektory môžete nájsť v našej firemnej knižnici pre formulárové komponenty, ktorú nájdete tu 📘.

context('Auth', () => {
	it('Sign up', () => {
		cy.clearLocalStorage()
		cy.intercept({
			method: 'POST',
			url: '/test-api/registration'
		}).as('registration')
		cy.visit('/signup')
		cy.setInputValue(FORM.REGISTRATION, 'email', `${generateRandomString(5)}_${user.emailSuffix}`)
		cy.setInputValue(FORM.REGISTRATION, 'password', user.password)
		cy.setInputValue(FORM.REGISTRATION, 'phone', user.phone)
		cy.clickButton('gdpr', FORM.REGISTRATION, true)
		cy.clickButton('marketing', FORM.REGISTRATION, true)
		cy.get('form').submit()
		cy.wait('@registration').then((interception: any) => {
			// check status code of registration request
			expect(interception.response.statusCode).to.equal(200)
			// take local storage snapshot
			cy.saveLocalStorage()
		})
		// check redirect to activation page
		cy.location('pathname').should('eq', '/activation')
	})

	it('Sign out', () => {
		cy.restoreLocalStorage()
		cy.intercept({
			method: 'POST',
			url: '/test-api/logout'
		}).as('authLogout')
		cy.visit('/')
		cy.get('.noti-my-account').click()
		cy.get('#logOut').click()
		cy.wait('@authLogout').then((interception: any) => {
			// check status code of logout request
			expect(interception.response.statusCode).to.equal(200)
			// check if tokens are erased
			assert.isNull(localStorage.getItem('refresh_token'))
			assert.isNull(localStorage.getItem('access_token'))
		})
		// check redirect to login page
		cy.location('pathname').should('eq', '/login')
	})

	it('Sign in', () => {
		cy.clearLocalStorage()
		cy.intercept({
			method: 'POST',
			url: '/test-api/login'
		}).as('authLogin')
		cy.visit('/login')
		cy.setInputValue(FORM.LOGIN, 'email', Cypress.env('auth_email'))
		cy.setInputValue(FORM.LOGIN, 'password', Cypress.env('auth_password'))
		cy.get('form').submit()
		cy.wait('@authLogin').then((interception: any) => {
			// check status code of login request
			expect(interception.response.statusCode).to.equal(200)
			// take local storage snapshot
			cy.saveLocalStorage()
		})
		// check redirect to home page
		cy.location('pathname').should('eq', '/')
	})
})

Záver

Verím, že vám tento článok priblížil E2E testovanie pomocou cypressu a uľahčí vám vašu prácu, v prípade že sa rozhodnete pre E2E testy.

Roman HaluškaFrontend developer