Základy programovania v JavaScripte
Lekcia 11
Milan Herda, 07/2020
Video k tejto lekcii:
Automatizované testovanie
Automatizované testovanie by malo byť neoddeliteľnou súčasťou programovania
Automatizované === nie ručné
Automatizované testy
- dajú sa spúšťať automaticky
- počas svojho behu nepotrebujú zásah človeka
- píšu ich programátori
- píšu ich testeri
Je veľa firiem, kde sa testy nepíšu, lebo...
- "tlačí nás čas"
- "veď sme to preklikali a testeri otestovali"
- "ak bude chyba, používatelia nahlásia"
- manažment nevyžaduje
- programátori nie sú zvyknutí
Testy sa píšu, pretože
- napísaný test je dôkaz, že kód sme otestovali a že funguje podľa očakávaní
- beh automatizovaného testu je rýchlejší ako ručné prebehnutie testovacieho scenáru
- testeri nie sú zaťažení repetitívnymi úlohami
- programátori môžu robiť odvážnejšie zmeny
- testy zvyšujú kvalitu napísaného kódu (ľahšie a rýchlejšie úpravy)
Testovacie nástroje pre JavaScript
- je ich veľa, veľa
- vhodné nástroje závisia aj od používaného JS frameworku
Cypress
Odbočka - lokálny web server
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.
Odbočka - lokálny 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
Odbočka - lokálny web 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
Odbočka - lokálny web server
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
Cypress
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.
Cypress.io - inštalácia a spustenie
npm install cypress
npx cypress open
Napíšte do príkazového riadku:
Cypress
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.
Cypress.io - otestovanie našej kalkulačky
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
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
describe a it
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:
- je opakovateľný
- je nezávislý na iných testoch (a teda aj poradí volania)
- neovplyvňuje iné testy
Cypress.io - otestovanie našej kalkulačky
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.visit otvorí URL a skontroluje statusový kód (očakáva 200) a typ dokumentu (text/html)
- cy.contains - nájde DOM element obsahujúci daný text
- cy.get - nájde DOM element podľa CSS selektoru
Cypress.io - otestovanie našej kalkulačky
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).
Úloha:
Napíšte druhý test (it), ktorý skontroluje, či je na stránke formulár pre zisťovanie väčšieho čísla.
Riešenie
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');
});
});
Interakcie na stránke
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');
Úloha
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:
- get - na získanie elementu
- should - na porovnanie hodnoty
Úloha
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:
- aserciu (tvrdenie) vo forme reťazca
- prípadnú hodnotu, ktorú chceme mať potvrdenú
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');
Riešenie
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?
Mocha poskytuje tzv. hooky
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:
- before - vykonáva sa raz a to skôr, než sa spustí prvý test v description bloku
- after - vykonáva sa raz a to po poslednom teste v description bloku
- beforeEach - vykonáva sa pred každým testom v description bloku
- afterEach - vykonáva sa po každom teste v description bloku
Mocha poskytuje tzv. hooky
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 () {/* ... */});
});
Úloha
Upravte doterajší kód tak, aby bol cy.visit vykonaný pred každým testom.
Riešenie
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);
});
});
Teória
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:
- nie je zadané číslo a človek klikne na tlačidlo
- je zadaná 0
- je zadané záporné číslo
- je zadané desatinné číslo
- nie je zadané číslo, ale namiesto neho reťazec
- musíme overiť, či existuje miesto, kde sa zobrazuje výsledok
- vieme formulár odoslať klávesou enter?
- a čokoľvek ďalšie a zmysluplné vám napadne
Úloha na doma
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.
- nie je zadané číslo a človek klikne na tlačidlo
- je zadaná 0
- je zadané záporné číslo
- je zadané desatinné číslo
- nie je zadané číslo, ale namiesto neho reťazec
- musíme overiť, či existuje miesto, kde sa zobrazuje výsledok
- vieme formulár odoslať klávesou enter?
Úloha na teraz
Napíšte test, ktorý otestuje základnú funkcionalitu formuláru pre zistenie väčšieho z dvoch čísel.
Riešenie
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);
});
Testovanie Karla
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
Testovanie Karla
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}');
Testovanie Karla
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 () {
// ...
});
});
Úloha
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
Riešenie
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
Úloha
Napíšte test na overenie fungovania stlačenia pravej šípky
Riešenie
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
Úloha
Napíšte test na overenie fungovania stlačenia ľavej šípky
Riešenie
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
Úloha
Napíšte test na overenie fungovania stlačenia šípky hore
Riešenie
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');
});
});
Úloha
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
Trošku si pomôžeme
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');
});
Riešenie
Záver
Kedy spúšťať testy?
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.
Nespomaľuje písanie testov čas vývoja?
Keď sa s testami začína a všetci sa ich učia, tak áno
Ale
- funkctionalitu treba aj tak otestovať
- počas vývoja sa testuje niekoľkokrát to isté - prečo si to neautomatizovať?
- ako dlho trvá fixovanie bugov, ktoré sa našli v produkcii?
- koľko peňazí stojí chyba nájdená v produkcii?
Písanie testov vedie k tvorbe kvalitnejšieho kódu
Existujúce testy znižujú množstvo chýb v kóde
Vývoj ide rýchlejšie, keď je existujúci kód kvalitnejší a bez chýb
Ďakujem za pozornosť
Všetok kód z dnešnej lekcie:
Školenie JS 2020 - lekcia 11
By Milan Herda
Školenie JS 2020 - lekcia 11
- 579