Lekcia 11
Milan Herda, 07/2020
Video k tejto lekcii:
Automatizované testovanie by malo byť neoddeliteľnou súčasťou programovania
Automatizované === nie ručné
Bolo by vhodné, keby naša stránka s Karlom bolo ozajstná webstránka a bežala na nejakom web serveri.
Cypress k nej tak bude pristupovať cez HTTP protokol, čiže rovnako, akoby k nej pristupoval, keby bola umiestnená na internete.
Nainštalujeme a spustíme si veľmi jednoduchý web server.
V adresári (priečinok, folder, zložka...), v ktorom máte uloženého Karla (index.html):
- otvorte príkazový riadok
- spustite príkaz:
npm install http-server
Tým sa nainštaluje http server. Teraz ho spustite príkazom:
npx http-server
*Počas testovania si nezatvárajte okno s týmto terminálom a ani neukončujte beh http serveru.
*Operačný systém alebo firewall antivírusu vás po spustení tohto príkazu môžu obťažovať s otázkou, či naozaj chcete otvoriť port 8080 alebo s podobnou otázkou: áno, chcete to
Teraz si vyskúšajte do prehliadača napísať jednu z URL adries, ktoré server vypísal.
Tip: namiesto 127.0.0.1 môžete napísať localhost
Teraz už naozaj
Inštalovali sme si na prvej hodine
Cypress je nástroj pre tzv. end-to-end testovanie web stránok.
To znamená, že netestuje váš kód izolovane, ale v kontexte celej aplikácie.
npm install cypress
npx cypress open
Napíšte do príkazového riadku:
Cypress po spustení cez npx cypress open otvorí webový prehliadač a ukáže vám zoznam testovacích scenárov.
Testovacie scenáre sú súbory s príponou .cy.js (defaultne v adresári cypress/e2e)
V týchto súboroch sa nachádza javascriptový kód, ktorým hovoríme cypressu, čo má robiť.
Keď si v cypresse vyberieme nejaký scenár, tak on začne vykonávať príkazy napísané v súbore a podľa toho bude ovládať prehliadač a kontrolovať výsledky.
Vytvoríme súbor cypress/integration/kalkulacka.cy.js
describe('kalkulacka', function () {
it('by mala dobre počítať', function () {
// kód testu
});
});
describe a it pochádzajú z frameworku Mocha
describe slúži na logické oddeľovanie skupín testov. Môže v sebe obsahovať viacero it a dokonca aj describe, ak potrebujeme robiť podskupiny.
it zabaľuje jeden test
it sa vykonávajú v takom poradí, ako sú zapísané v súbore
Keďže it predstavuje jeden test, tak musí spĺňať požiadavky na test, ktoré sú platné pre všetky automatizované testy:
Pracujeme v súbore cypress/integration/kalkulacka.spec.js
describe('kalkulacka', function () {
it('obsahuje formulár pre výpočet druhej mocniny', function () {
cy.visit('http://localhost:8080/kalkulacka.html');
cy.contains('Druhá mocnina');
cy.get('#form-power');
});
});
Príkazy cy.* pochádzajú od cypressu a slúžia na manipuláciu so stránkou.
cy.* príkazov je veľa. Kompletný zoznam je v dokumentácii:
Ak nejaký cy príkaz spadne alebo nenájde daný element, tak sa to považuje za padnutý test.
Práve bežiaci it test teda neprejde, zruší sa jeho vykonávanie a pokračuje sa na ďalší it.
Tieto príkazy je možné reťaziť (tj. vieme za nimi napísať bodku a pokračovať ďalším príkazom).
Napíšte druhý test (it), ktorý skontroluje, či je na stránke formulár pre zisťovanie väčšieho čísla.
Pracujeme v súbore cypress/integration/kalkulacka.spec.js
describe('kalkulacka', function () {
it('obsahuje formulár pre výpočet druhej mocniny', function () {
cy.visit('http://localhost:8080/kalkulacka.html');
cy.contains('Druhá mocnina');
cy.get('#form-power');
});
it('obsahuje formulár pre zisťovanie väčšieho čísla', function () {
cy.visit('http://localhost:8080/kalkulacka.html');
cy.contains('Zisti väčšie');
cy.get('#form-max');
});
});
Cypress nám umožňuje s elementami na stránke aj interagovať (klikať na ne, písať do nich, vyberať si zo selectboxov a pod.)
Robí sa to pomocou špeciálnych commandov, tu je k tomu dokumentácia:
V spojení s reťazením príkazov tak vieme napr. najskôr pohľadať element a potom do neho písať
cy.get('#form-search > input[name=search]').type('praca bratislava');
Napíšte ďalší test, ktorý overí, že formulár pre druhú mocninu počíta správne.
Budete potrebovať skontrolovať, že vypočítaný výsledok je taký, ako očakávate.
To urobíte tak, že sa pozriete, čo sa zapísalo do elementu s id power-result
K tomu budete potrebovať dva zreťazené cy príkazy:
Napíšte ďalší test, ktorý overí, že formulár pre druhú mocninu počíta správne.
Príkaz should očakáva jeden alebo dva argumenty:
Asercie (asserty) si cypress vypožičal z knižnice chai.
Tu je dokumentácia: https://www.chaijs.com/api/bdd/
cy.get('#selektor').should('not.be.empty');
cy.get('#selektor').should('be.string', 'ahoj svet');
cy.get('#selektor').should('have.string', 'ahoj');
Napíšte ďalší test, ktorý overí že formulár pre druhú mocninu počíta správne.
it('formulár pre druhú mocninu funguje', function () {
cy.visit('http://localhost:8080/kalkulacka.html');
cy.get('#form-power input').type(2);
cy.get('#form-power button').click();
cy.get('#result-power').should('be.text', 4);
});
Pracujeme v súbore cypress/integration/kalkulacka.spec.js
describe('kalkulacka', function () {
it('obsahuje formulár pre výpočet druhej mocniny', function () {
cy.visit('http://localhost:8080/kalkulacka.html');
cy.contains('Druhá mocnina');
cy.get('#form-power');
});
it('obsahuje formulár pre zisťovanie väčšieho čísla', function () {
cy.visit('http://localhost:8080/kalkulacka.html');
cy.contains('Zisti väčšie');
cy.get('#form-max');
});
it('formulár pre druhú mocninu funguje', function () {
cy.visit('http://localhost:8080/kalkulacka.html');
cy.get('#form-power input').type(2);
cy.get('#form-power button').click();
cy.get('#result-power').should('be.text', 4);
});
});
Toto sa nám opakuje v každom teste, nevieme to nejako dať vykonať automaticky?
Pomocou hooku vieme dať vykonávať náš vlastný kód v určitých momentoch, kedy mocha prechádza cez testy.
Tieto hooky sú štyri:
Hooky sa používajú tak, že vo vnútri description bloku zavoláte funkciu nazvanú podľa vybraného hooku a ako argument jej odovzdáte anonymnú funkiu, v ktorej je váš kód
description('dôležitý test', function () {
beforeEach(function () {
console.log('Tento kód sa vykoná pred každým testom');
});
it('...', function () {/* ... */});
it('...', function () {/* ... */});
it('...', function () {/* ... */});
});
Upravte doterajší kód tak, aby bol cy.visit vykonaný pred každým testom.
Upravte doterajší kód tak, aby bol cy.visit vykonaný pred každým testom.
describe('kalkulacka', function () {
beforeEach(function () {
cy.visit('http://localhost:8080/kalkulacka.html');
});
it('obsahuje formulár pre výpočet druhej mocniny', function () {
cy.contains('Druhá mocnina');
cy.get('#form-power');
});
it('obsahuje formulár pre zisťovanie väčšieho čísla', function () {
cy.contains('Zisti väčšie');
cy.get('#form-max');
});
it('formulár pre druhú mocninu funguje', function () {
cy.get('#form-power input').type(2);
cy.get('#form-power button').click();
cy.get('#result-power').should('be.text', 4);
});
});
Testy by mali otestovať svoju testovanú jednotku zo všetkých strán, vyskúšať všetky možné aj nemožné vstupy a situácie a rôzne okrajové podmienky.
Keby sme formulár chceli otestovať pre reálnu aplikáciu, tak testami musíme pokryť tieto situácie:
Napíšte test na niektorú z vymenovaných situácií.
Ak sa stránka nebude správať tak, ako by ste očakávali, upravte kód starajúci sa o výpočet alebo zobrazenie výsledku.
Napíšte test, ktorý otestuje základnú funkcionalitu formuláru pre zistenie väčšieho z dvoch čísel.
Pracujeme v súbore cypress/integration/kalkulacka.spec.js
it('formulár pre zistenie väčšieho čísla funguje', function () {
cy.get('input[name=first_number]').type(2);
cy.get('input[name=second_number]').type(5);
cy.get('#form-max button').click();
cy.get('#result-max').should('be.text', 5);
});
Ukážeme si, ako otestovať ovládanie Karla pomocou klávesnice
Už vieme, ako písať text do formulárových inputov
cy.get('form input').type('hello world');
Pomocou tohto istého mechanizmu vieme "písať" aj špeciálne klávesy. Stačí, ak ich názov uvedieme v krútených zátvorkách.
Tu je zoznam kláves, ktoré cypress podporuje: https://docs.cypress.io/api/commands/type#Arguments
Už potrebujeme iba element, do ktorého budeme "písať".
Karel si svoje listenery zavesil na element document
document.addEventListener('keydown', function (event) {
switch (event.code) {
case 'ArrowUp':
// ...
}
});
Ten ale podľa dokumentácie cypress-u použiť nemôžeme a musíme použiť body
Neviem, prečo. Moc to nevysvetľujú a document naozaj to nefunguje :)
cy.get('body').type('{uparrow}');
Vytvoríme nový spec súbor cypress/integration.karel.spec.js
describe('karel', function () {
beforeEach(function () {
cy.visit('http://localhost:8080');
});
it('začína na pozícii [19,0] otočený na sever', function () {
// ...
});
});
Pracujeme v súbore cypress/integration.karel.spec.js
describe('karel', function () {
beforeEach(function () {
cy.visit('http://localhost:8080');
});
it('začína na pozícii [19,0] otočený na sever', function () {
// ...
});
});
Doplňte telo úvodného testu
Pracujeme v súbore cypress/integration.karel.spec.js
describe('karel', function () {
beforeEach(function () {
cy.visit('http://localhost:8080');
});
it('začína na pozícii [19,0] otočený na sever', function () {
cy.get('#row').should('be.text', 19);
cy.get('#col').should('be.text', 0);
cy.get('#direction').should('be.text', 'sever');
});
});
Doplňte telo úvodného testu
Využijeme dáta v informačnom paneli pod tlačidlami
Napíšte test na overenie fungovania stlačenia pravej šípky
Napíšte test na overenie fungovania stlačenia pravej šípky
describe('karel', function () {
beforeEach(function () {
cy.visit('http://localhost:8080');
});
it('začína na pozícii [19,0] otočený na sever', function () {
// ...
});
it('šípka doprava otočí karla', function () {
cy.get('body').type('{rightarrow}');
cy.get('#row').should('be.text', 19);
cy.get('#col').should('be.text', 0);
cy.get('#direction').should('be.text', 'východ');
cy.get('body').type('{rightarrow}');
cy.get('#direction').should('be.text', 'juh');
cy.get('body').type('{rightarrow}');
cy.get('#direction').should('be.text', 'západ');
cy.get('body').type('{rightarrow}');
cy.get('#direction').should('be.text', 'sever');
});
});
Pracujeme v súbore cypress/integration.karel.spec.js
Napíšte test na overenie fungovania stlačenia ľavej šípky
Napíšte test na overenie fungovania stlačenia ľavej šípky
describe('karel', function () {
// ...
it('šípka doľava otočí karla', function () {
cy.get('body').type('{leftarrow}');
cy.get('#row').should('be.text', 19);
cy.get('#col').should('be.text', 0);
cy.get('#direction').should('be.text', 'západ');
cy.get('body').type('{leftarrow}');
cy.get('#direction').should('be.text', 'juh');
cy.get('body').type('{leftarrow}');
cy.get('#direction').should('be.text', 'východ');
cy.get('body').type('{leftarrow}');
cy.get('#direction').should('be.text', 'sever');
});
});
Pracujeme v súbore cypress/integration.karel.spec.js
Napíšte test na overenie fungovania stlačenia šípky hore
describe('karel', function () {
// ...
it('šípka hore posunie karla dopredu', function () {
cy.get('body').type('{uparrow}');
cy.get('#row').should('be.text', 18);
cy.get('#col').should('be.text', 0);
cy.get('#direction').should('be.text', 'sever');
cy.get('body').type('{rightarrow}');
cy.get('body').type('{uparrow}');
cy.get('#row').should('be.text', 18);
cy.get('#col').should('be.text', 1);
cy.get('#direction').should('be.text', 'východ');
cy.get('body').type('{rightarrow}');
cy.get('body').type('{uparrow}');
cy.get('#row').should('be.text', 19);
cy.get('#col').should('be.text', 1);
cy.get('#direction').should('be.text', 'juh');
cy.get('body').type('{rightarrow}');
cy.get('body').type('{uparrow}');
cy.get('#row').should('be.text', 19);
cy.get('#col').should('be.text', 0);
cy.get('#direction').should('be.text', 'západ');
});
});
Napíšte test na overenie fungovania stlačenia medzerovníka
Ako sa dostaneme k informácii, či je bunka označená alebo nie?
Bunka je označená, ak jej element td má triedu znacka.
cy.get('správny td element').should('have.class', 'znacka');
Len sa potrebujeme dostať k správnemu elementu
Celkom by pomohlo, keby sme každú bunku vedeli jednoznačne identifikovať.
Na to vieme využiť číslo riadku a číslo stĺpca, lebo každá bunka má túto kombináciu unikátnu.
Upravíme v index.html generovanie buniek tak, aby každé td obsahovala data-cy-position atribút s číslom riadku a stĺpca
Ak potrebujeme v teste pristúpiť k nejakému HTML elementu a tento nie je jednoznačne identifikovateľný cez id alebo nejaké klasické atribúty, aby sme danému elementu dali atribút nazvaný data-cy* alebo data-testid
<!-- bunka bez značky tak bude vyzerať takto: -->
<td data-cy-position="10:4">
<!-- a takto bunka so značkou: -->
<td data-cy-position="10:4" class="znacka">
const vygenerujBunku = function (riadok, stlpec) {
let bunka = '<td data-cy-position="' + riadok + ':' + stlpec + '">';
if (mapa.bunky[stlpec][riadok].jeOznacene) {
bunka = '<td data-cy-position="' + riadok + ':' + stlpec + '" class="znacka">';
}
if (karel.riadok === riadok && karel.stlpec === stlpec) {
bunka = bunka + vygenerujKarla();
}
bunka = bunka + '</td>';
return bunka;
};
Upravíme funkciu vygenerujBunku v súbore index.html
it('medzerovník položí značku na prázdne miesto', function () {
cy.get('body').type(' ');
cy.get('[data-cy-position="19:0"]').should('have.class', 'znacka');
});
it('medzerovník zdvihne značku z označeného miesta', function () {
cy.get('body').type(' ');
cy.get('body').type(' ');
cy.get('[data-cy-position="19:0"]').should('not.have.class', 'znacka');
});
Pri vývoji spúšťame priebežne testy aspoň pre nový kód.
Po dokončení tasku/väčšieho celku spustíme všetky testy.
Keď sa s testami začína a všetci sa ich učia, tak áno