/
Unit Testing

Unit Testing

Úvod

Keď generujeme komponenty/triedy/servicy pomocou Angular Schém ( pravým tlačidlom na priečinok, kde chcem vytvoriť nový komponent - New/Angular Schematics/ class, component, service) tak angular k vygenerovaným súborom pridá aj súbory typu *.spec.ts. Tieto súbory sú na testovanie, a sám angular do nich priamo pripraví potrebné veci pre spúštanie testov, ba aj priamo prvý test ‘should create’, ktorý testuje, či sa daný komponent vytvorí.

Testy sa v Angular aplikáciach spúštajú nasledovným príkazom v termináli:

ng test

Tento príkaz spustí Karma test runner, ktorý sa stará o spúštanie testov a o všetkú konfiguráciu. Jednotlivé testy sa píšu v testovacom frameworku Jasmine.

Pre začiatok si musíme nastaviť Karmu, aby sme mohli testy spúštať v Jenkins pipeline a aby sa nám pokrytie testov (coverage) posielalo pri Sonar analýze do Sonaru.

Konfigurácia Karma.conf.js

Otvoríme si súbor karma.conf.js, ktorý sa nachádza v roote projektu. Kôli exportu testov do SonarQube potrebujeme do karmy pridať nový reporter. Na začiatku konfigu potrebujeme pridať junit reporter do plugins, a nainštalovať ho:

... plugins: [ ... require("karma-junit-reporter"), ... ], ...

Nainštalujeme ho pomocou príkazu npm install a jeho flagy --save-dev, ktorá zabezpečí že sa uloží ako dev dependencia v package.json:

npm install --save-dev karma-junit-reporter

Pod už existujúci reporter coverageIstanbulReporter pridáme nasledujúci kus kódu:

... junitReporter: { outputDir: "./coverage/<meno_projektu>", // results will be saved as $outputDir/$browserName.xml outputFile: "JUNITX-test-report.xml", // if included, results will be saved as $outputDir/$browserName/$outputFile suite: "", // suite will become the package name attribute in xml testsuite element useBrowserName: false, // add browser name to report and classes names nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element properties: {}, // key value pair of properties to add to the <properties> section of the report xmlVersion: null, // use '1' if reporting to be per SonarQube 6.2 XML format }, customLaunchers: { ChromeHeadlessCI: { base: 'ChromeHeadless', flags: [ '--no-sandbox', '--headless', '--disable-gpu', '--disable-translate', '--disable-extensions', '--disable-dev-shm-usage' ] } }, ...

Prvá čast nám zabezpečuje, že testy budú generovať report vo formáte JUNIT pre Jenkins a SonarQube. Treba ale myslieť na to, že treba v JUNIT reportéri nastavit outputDir presne tak isto, ako je nastavený v coverageIstanbulReporter. Keď sme nastavili túto cestu, môžme sa pozrieť na druhú časť tohto kódu. Tá nám zabezpečuje to, že pri spúštaní testov sa nebude otvárať prehliadač, a že celá jeho kompilácia bude prebiehať v príkazovom riadku. Toto nám umožní púštat testy v Jenkins pipelinách. Teraz ešte potrebujeme pridať klúčové prvky do tohto konfigu.

Ako dalšie potrebujeme pridať naše reportéry do skupiny reporters, zmeniť browser a singleRun property v konfigu:

... reporters: ["progress", "kjhtml", "coverage-istanbul", "junit"], ... browsers: ['ChromeHeadlessCI'], singleRun: true, ...

Ako posledné potrebujeme nastaviť a povedať angularu, že chceme aby nám reportoval pokrytie kódu testami, preto otvoríme angular.json v projekte a v časti test nastavíme nasledujúce propreties:

... "test": { ... "sourceMap": true, "codeCoverage": true, ... }, ...

Konfiguráciu máme za sebou, podme si vysvetliť ako fungujú testy.

Testy v Jasmine

Dokumentácia frameworku Jasmine nie je veľká, preto stojí zato si ju pozrieť aby ste vedeli aké všeliaké možnosti na vytváranie testov máte. Isto stoji za zmienku aj Angular Testing dokumentácia, ktorá už ale neni na krátke čítanie.

Najjednoduchšie testy sú testy tried, pretože triedy ako také vačšinou vôbec nepodliehajú Angularu, to znamená že do nich Angular neinjectuje servicy pomocou Dependency Injection.

Takýto test vygeneruje angular pri vytvorení triedy s názvom class:

import { Class } from './class'; describe('Class', () => { it('should create an instance', () => { expect(new Class()).toBeTruthy(); }); });

Funkcia describe(description, specDefinitions) slúži na vytvorenie skupiny testov, niekedy takéto skupiny voláme aj test suite. Prvý vstup description je string, ktorým opisujeme daný suite, vačšinou teda názov triedy/komponentu/servicy, aby bol unikátny. Druhým vstupov je anonymná funkcia , v ktorej bližšie špecifikujeme testy. Môžeme si všimnúť že sa tu nachádza funkcia it.

Funkciou it(description, testFunction - opt, timeout - opt) definujeme náš test. Prvým vstupom je popis testy, vačšinou vy mal byť vystižný a mal by popisovať čo daný test testuje. Druhý vstup je funkcia, ktorá je v podstate telom testu. Priamo v tele funkcie už píšeme naše expectatons- teda naše očakávania, ako naše volanie funkcie/konštruktoru dopadne. Tieto expectations píšeme pomocou funkcie expect(actual), ktorá nám vracia objekt ktorý môžme porovnávať s očakávaným objektom.

Telo funkcie sa vykoná raz, a bez toho aby sme mu oznámili že má počkať, nečaká na žiadne asynchonne volania. Vtedy sa ako vstup dotela funkcie dáva (done: DoneFn), ktorá spôsobý to, že telo funkcie čaká na zavolanie done(), aby vedelo že test je ukončený. Poprípade sa dá ešte pred volanie tela funkcie napísať async, a tým funkcí oznámime že bude asynchrónna.

Tieto porovnania robíme pomocou takzvaných Matchers, ktoré nám pomáhajú porovnať vracajúci objekt s objektom, ktorý očakávame. Odporúčam prejsť si celý zoznam matchers, lebo sú to funkcie ktoré pri testovaní budete používať najčastejšie. [ zoznam tu ].

Oplatí sa mi ale spomenút pár tychto funkcíí:

  • expect().toBeTruthy(); - Funkcia ktorá býva vždy na should create funkciách, očakáva že daná trieda/komponent/servica bude vytvorená a nebude undefined.

  • expect().toBeTrue(); - Očakáva že sa vráti true hodnota

  • expect().toBe(expected); - porovnáva vacanú hodnotu s expected pomocou ===

  • expect().toBeNaN(); - vrátená hodnota bude NaN

  • expect().toEqual(expected); - očakáva že vrátená hodnota bude rovnaká ako expected, porovnanie prebieha pomocou deep equality comparison.

  • expect().toHaveBeenCalled(expected); - Vstupom do funkcie je spy objekt (vysvetlenie nižšie), a funkcia očakáva že daný spy bude zavolaný

  • expect().toThrowError(message); - očakáva, že dané volanie funkcie vyhodí error z daným message

S pokročílejšímy triedami a funkciami prichádzajú aj pokročilejšie metódy testovania. Medzi dosť potrebné funkcie v dalšej časti patria before a after funkcie, ktoré bývajú aj v generovaných testoch Angularom:

  • beforeEach(function - opt, timeout - opt) - funkcia vykonávaná pred každým testom

  • beforeAll(function - opt, timeout - opt) - funkcia vykonaná pred začatím test suite

  • afterEach(function - opt, timeout - opt) - funkcia vykonávaná po každom teste

  • afterAll(function - opt, timeout - opt) - funkcia vykonaná po skončení test suite

Na testovanie napríklad void funkcí vieme použiť takzavého špeha, alebo po anglicky Spy. Vieme ho použiť na špehovanie, napríklad či sa dáka metóda ktorú voláme z danej funckie zavolá, alebo vieme pomocou neho aj stubnút výsledok danej volanej funkcie. [Ak by niekto potreboval vediet rozdiel medzi mock a stub tak kliknite na tento dlhý link] Spy existuj

 

describe('Test spy', () => { let spy: jasmine.Spy; it('should create an instance', () => { expect(new Class()).toBeTruthy(); }); });

Related content