Articles

Webkomponensek tesztelési munkafolyamata

Amikor valamit mások használatára szállítunk, felelősséget vállalunk a biztonságos és stabil kód átadásáért. Ennek egyik módja a kód tesztelése.

Nem számít, milyen kicsi – nem számít, milyen egyszerű a projekted, ideális esetben mindig kellenek tesztek.

Igen, tudom, hogy a valóság keményen üt, és sok olyan eset lesz, amikor ez nem történik meg – de mindig törekedned kell arra, hogy legyenek tesztek

Jogi nyilatkozat

Ebben a bemutatóban egy beviteli elem egyszerű változatát fogjuk elkészíteni. A végére elsajátíthatod azokat a készségeket és ismereteket, amelyekkel az open-wc tesztelési eszközeit a gyakorlatban is alkalmazhatod; és egy szilárd, hozzáférhető és jól tesztelt bemeneti komponenst építhetsz.

Figyelmeztetés

Ez egy mélyreható bemutató, amely bemutat néhány buktatót és nehéz esetet a webkomponensekkel való munka során. Ez mindenképpen haladóbb felhasználóknak szól. Alapvető ismeretekkel kell rendelkeznie a LitElement és a JSDoc típusokról. Ha van fogalmad arról, hogy mi az a Mocha, Chai BDD, Karma, az is segíthet egy kicsit.

Gondolkodunk azon, hogy ennek egy könnyebben emészthető változatát is közzétesszük, így ha ezt szeretnéd látni – jelezd a hozzászólásokban.

Ha szeretnél játszani – az összes kód megtalálható a githubon.

Kezdjünk bele!

Futtasd a konzolodban

$ npm init @open-wc# Results in this flow✔ What would you like to do today? › Scaffold a new project✔ What would you like to scaffold? › Web Component# Select with space! "Testing" => just enter will move one with no selection✔ What would you like to add? › Testing✔ Would you like to scaffold examples files for? › Testing✔ What is the tag name of your application/web component? … a11y-input✔ Do you want to write this file structure to disk? › YesWriting..... done✔ Do you want to install dependencies? › No

A további részletekért lásd https://open-wc.org/testing/.

Törölje src/A11yInput.js

Módosítsa a src/a11y-input.js-t:

és a test/a11y-input.test.js-t:

Az eddigi tesztjeink egyetlen tulajdonságból (a label tulajdonságból) és egyetlen állításból (expect) állnak. A karma és chai BDD szintaxisát használjuk, így a tesztek halmazait (it) a tulajdonságok vagy API-k alá csoportosítjuk, amelyekre vonatkoznak (describe).

Lássuk, hogy minden megfelelően működik-e a futtatással: npm run test.

Félelmetes – ahogy vártuk (🥁), van egy hibás tesztünk 🙂

Kapcsoljunk át figyelő üzemmódba, amely folyamatosan futtatja a teszteket, amikor változtatunk a kódunkon.

npm run test:watch

A fenti videóban a következő kódot adtuk hozzá a src/a11y-input.js-hez:

static get properties() { return { label: { type: String }, };}constructor() { super(); this.label = '';}

So messze, ilyen jó? Még mindig velünk vagy? Nagyszerű! Növeljük egy kicsit a játékot…

Teszt hozzáadása az árnyék DOM-hoz

Adjunk hozzá egy állítást, hogy teszteljük az elemünk árnyék gyökerének tartalmát.

Ha biztosak akarunk lenni abban, hogy az elemünk ugyanúgy viselkedik/megjelenik, meg kell győződnünk arról, hogy a dom szerkezete ugyanaz marad.
Ezért hasonlítsuk össze az aktuális árnyék dom-ot azzal, amit szeretnénk, hogy legyen.

A vártaknak megfelelően azt kapjuk:

Szóval implementáljuk ezt az elemünkbe.

render() { return html` <slot name="label"></slot> <slot name="input"></slot> `;}

Interes, a tesztnek zöldnek kellene lennie… de nem az 🤔 Nézzük meg.

Elképzelhető, hogy észrevetted azokat a fura üres komment <!----> tageket. Ezek olyan jelölések, amelyeket a lit-html arra használ, hogy megjegyezze, hol vannak a dinamikus részek, így hatékonyan frissíthető. A teszteléshez azonban kissé bosszantó lehet ezzel foglalkozni.

Ha a innerHTML-t használnánk a DOM összehasonlítására, akkor egyszerű karakterlánc-egyenlőségre kellene hagyatkoznunk. Ilyen körülmények között pontosan meg kellene egyeznünk a generált DOM szóközökkel, megjegyzésekkel stb.; más szóval: tökéletes egyezésnek kell lennie. Valójában csak azt kell tesztelnünk, hogy a renderelni kívánt elemek renderelve legyenek. Az árnyékgyökér szemantikai tartalmát akarjuk tesztelni.

Szóval próbáljuk ki 💪

// old:expect(el.shadowRoot.innerHTML).to.equal(`...`);// new:expect(el).shadowDom.to.equal(` <slot name="label"></slot> <slot name="input"></slot>`);

Bam 🎉

a11y input ✔ has by default an empty string as a label ✔ has a static shadowDom

Hogyan működik a shadowDom.to.equal()?

  1. Kapja az innerHTML árnyékgyökeret
  2. Elveszi (tulajdonképpen, a böngésző elemzi – nincs szükség könyvtárra)
  3. Normalizálja (potenciálisan minden tag/tulajdonság a saját sorában)
  4. Megbontja és normalizálja a várt HTML karakterláncot
  5. Elküldi mindkét normalizált DOM karakterláncot a chai alapértelmezett compare függvényének
  6. Hiba esetén, csoportosítja, és az esetleges különbségeket áttekinthető módon megjeleníti

Ha többet szeretnél tudni, nézd meg a semantic-dom-diff dokumentációját.

A “könnyű” DOM tesztelése

A könnyű DOM-mal pontosan ugyanezt tudjuk csinálni. (Azzal a DOM-mal, amelyet a felhasználó vagy az alapértelmezettjeink, azaz az elem children-je biztosít számunkra.)

És valósítsuk meg.

Szóval teszteltük a fény és árnyék domunkat 💪 és a tesztjeink tisztán futnak 🎉

Megjegyezzük: A DOM API használata egy lit-elem életciklusában anti-pattern, azonban az a11y lehetővé tételéhez ez egy valós felhasználási eset lehet – mindenesetre illusztrációs célokra nagyszerű

Az elemünk használata egy alkalmazásban

Szóval most, hogy van egy alapvető a11y bemenetünk, használjuk – és teszteljük – az alkalmazásunkban.

Újra egy vázzal kezdünk src/my-app.js

És a tesztünk a test/my-app.test.js-ben;

A teszt futtatása => sikertelen, majd hozzáadjuk az implementációt a src/a11y-input.js

render() { return html` <h1>My Filter App</h1> <a11y-input></a11y-input> `;}

De jaj ne! Ennek most zöldnek kellene lennie…

Mi történik?
Emlékszel arra, hogy volt egy speciális tesztünk, hogy biztosítsuk az a11y-bemenet fény-domját?
Szóval még ha a felhasználó csak <a11y-input></a11y-input>-t tesz is a kódjába – ami valójában kijön az

<a11y-input> <label slot="label"></label> <input slot="input"></a11y-input>

pl. a a11y-input valójában csomópontokat hoz létre a my-app shadow domodon belül. Képtelenség! Az itteni példánk esetében azt mondjuk, hogy ezt akarjuk.
Szóval hogyan tudjuk mégis tesztelni?

Szerencsére a .shadowDom-nek van egy másik ász a tarsolyában; lehetővé teszi, hogy figyelmen kívül hagyjuk a dom egyes részeit.

expect(el).shadowDom.to.equal(` <h1>My Filter App</h1> <a11y-input></a11y-input>`, { ignoreChildren: });

A következő tulajdonságokat is megadhatjuk:

  • ignoreChildren
  • ignoreTags
  • ignoreAttributes (globálisan vagy meghatározott címkékre vonatkozóan)

További részletekért lásd: semantic-dom-diff.

Snapshot tesztelés

Ha sok nagy dom fád van, akkor az összes kézzel írt várakozás megírása/fenntartása nehéz lesz.
Azért, hogy ebben segítsünk, vannak félig/automatikus snapshotok.

Ha tehát megváltoztatjuk a kódunkat

// fromexpect(el).shadowDom.to.equal(` <slot name="label"></slot> <slot name="input"></slot>`);// toexpect(el).shadowDom.to.equalSnapshot();

Ha most a npm run test futtatásával létrehoz egy __snapshots__/a11y input.md fájlt, és valami ilyesmivel tölti fel

# `a11y input`#### `has a static shadowDom```html<slot name="label"></slot><slot name="input"></slot>``

Azt, amit korábban kézzel írtunk, most automatikusan generálhatjuk init-en vagy kényszeresen a npm run test:update-snapshots segítségével.

Ha a __snapshots__/a11y input.md fájl már létezik, akkor összehasonlítja a kimenettel, és hibaüzeneteket kapunk, ha a html kimenetünk megváltozott.

A további részletekért lásd a semantic-dom-diff.

Azt hiszem, most már elég volt a dom fák összehasonlításáról…
Itt az ideje a változásnak 🤗

Code coverage

Egy másik hasznos metrika, amit az open-wc beállítással való tesztelés során kapunk, a code coverage.
Szóval mit jelent és hogyan kaphatjuk meg? A kódlefedettség azt méri, hogy a kódunk mekkora részét ellenőrzik a tesztek. Ha van olyan sor, utasítás, függvény vagy elágazás (pl. if/else utasítás), amelyet a tesztjeink nem fednek le, az befolyásolja a lefedettségi pontszámunkat.
Egy egyszerű npm run test elég, és a következőket kapjuk:

=============================== Coverage summary ===============================Statements : 100% ( 15/15 )Branches : 100% ( 0/0 )Functions : 100% ( 5/5 )Lines : 100% ( 15/15 )================================================================================

Ami azt jelenti, hogy a kódunk utasításainak, elágazásainak, függvényeinek és sorainak 100%-át lefedik a tesztek. Elég szép!

Menjünk tehát a másik irányba, és adjunk hozzá kódot a src/a11y-input.js-hez, mielőtt hozzáadnánk egy tesztet. Tegyük fel, hogy közvetlenül az egyéni elemünkön keresztül akarunk hozzáférni a bemenetünk értékéhez, és amikor az értéke ‘cat’, akkor naplózni akarunk valamit.

Ez egy merőben más eredmény

A lefedettségünk sokkal alacsonyabb, mint korábban. A tesztparancsunk még akkor is sikertelen, ha az összes teszt sikeresen lefutott.
Ez azért van, mert az open-wc konfigurációja alapértelmezés szerint 90%-os küszöbértéket állít be a kódlefedettségre.

Ha javítani akarjuk a lefedettséget, teszteket kell hozzáadnunk – tehát tegyük meg

uh oh 😱 javítani akartuk a lefedettséget, de most először ki kell javítanunk egy tényleges hibát 😞

Ez váratlanul ért… első ránézésre nem igazán tudom, hogy ez mit jelent…. jobb, ha ellenőrzünk néhány tényleges csomópontot és megvizsgáljuk őket a böngészőben.

Hibakeresés a böngészőben

Amikor a watch segítségével futtatjuk a tesztünket, a karma beállít egy állandó böngészőkörnyezetet, amelyben a teszteket futtatjuk.

  • Győződjünk meg róla, hogy a npm run test:watch
  • látogatás http://localhost:9876/debug.html

Egy ilyesmit kell látnunk

A bekarikázott play gombra kattintva csak egy-egy tesztet futtathatunk.

Szóval nyissuk meg a Chrome Dev Tools-t (F12) és tegyünk egy debuggert a tesztkódba.

Dang… a hiba még ez előtt a pont előtt történik…
Az ilyen “fatális” hibák kicsit nehezebbek, mivel ezek nem hibás tesztek, hanem a teljes komponensed egyfajta teljes összeolvadása.

Oké, tegyünk egy kis kódot közvetlenül a setter-ba.

set value(newValue) { debugger;

Jól van, ez működött, így a chrome konzolunkba azt írjuk console.log(this) Nézzük, mi van itt

<a11y-input> #shadow-root (open)</a11y-input>

Ahh, megvan – a shadow dom még nem renderelt, amikor a setter meghívásra kerül.
Szóval legyünk biztosak és adjunk hozzá egy ellenőrzést előtte

set value(newValue) { if (newValue === 'cat') { console.log('We like cats too :)'); } if (this.inputEl) { this.inputEl.value = newValue; }}

A fatel hiba eltűnt 🎉
De most van egy sikertelen tesztünk 😭

✖ can set/get the input value directly via the custom elementAssertionError: expected '' to equal 'foo'

A taktika megváltoztatására lehet szükségünk 🤔
Egy külön value tulajdonságként adhatjuk hozzá és szükség esetén szinkronizálhatjuk.

És végre újra munkába állunk! 🎉

ok bug fixed – can we please get back to coverage? thank you 🙏

Back to coverage

With this added test we made some progress.

However we are still not fully there – the question is why?

To find out open coverage/index.html in your browser. Nincs szükség webszerverre, csak nyisd meg a fájlt a böngésződben – mac-en ezt a parancssorból is megteheted a open coverage/index.html

Egy ilyesmit fogsz látni

Mihelyt rákattintasz a a11y-input.js-re, soronként kapsz egy információt arról, hogy milyen gyakran hajtották végre őket.
Így azonnal láthatjuk, hogy mely sorokat nem hajtották még végre a tesztjeink.

Szóval adjunk hozzá egy tesztet erre

=============================== Coverage summary ===============================Statements : 100% ( 24/24 )Branches : 75% ( 3/4 )Functions : 100% ( 7/7 )Lines : 100% ( 24/24 )================================================================================

Az utasításoknál már újra 100%-osak vagyunk, de az elágazásoknál még mindig hiányzik valami.
Lássuk miért?

Ez E azt jelenti, hogy else path not taken.
Amikor tehát a függvény update meghívásra kerül, mindig van egy value tulajdonság a changedProperties-ben.

Megvan a label is, így érdemes tesztelni. 👍

bumm 100% 💪 nyertünk 🥇

=============================== Coverage summary ===============================Statements : 100% ( 24/24 )Branches : 100% ( 4/4 )Functions : 100% ( 7/7 )Lines : 100% ( 24/24 )================================================================================

De várjunk csak, még be sem fejeztük a fenti tesztet – a kód még mindig

 // somehow check that console.log was called

Hogyhogy 100%-os a tesztlefedettségünk?

Próbáljuk meg először megérteni, hogyan működik a kódlefedettség 🤔
A kódlefedettség mérésének módja a instrumentation egy formájának alkalmazása. Röviden, mielőtt a kódunk végrehajtásra kerül, megváltozik (instrumented), és valahogy így viselkedik:

Megjegyzés: Ez egy szuper leegyszerűsített verzió a szemléltetés kedvéért.

Gyakorlatilag a kódunk sok-sok flaggel lesz tele. Az alapján, hogy mely flagek váltanak ki, egy statisztika jön létre.

A 100%-os tesztlefedettség tehát csak azt jelenti, hogy minden sorod a kódodban legalább egyszer végrehajtásra került, miután az összes teszted befejeződött. Ez nem jelenti azt, hogy mindent teszteltél, vagy hogy a tesztjeid helyes állításokat tesznek.

Szóval annak ellenére, hogy már 100%-os kódlefedettségünk van, még mindig javítani fogjuk a naplótesztünket.

A kódlefedettségre ezért inkább úgy kell tekintened, mint egy eszközre, amely csak útmutatást és segítséget nyújt néhány hiányzó teszt kiszűréséhez, nem pedig a kód minőségének kemény garanciája.

Kémkedés a kód után

Ha azt akarod ellenőrizni, hogy egy függvényt milyen gyakran vagy milyen paraméterekkel hívnak, azt kémkedésnek hívják.
Az open-wc ajánlja a tiszteletre méltó sinon csomagot, amely számos eszközt biztosít a kémkedéshez és más kapcsolódó feladatokhoz.

npm i -D sinon

Szóval létrehozol egy kémet egy adott objektumra, és aztán ellenőrizheted, hogy milyen gyakran hívják meg.

Uh oh… a teszt sikertelen:

AssertionError: expected 0 to equal 1

Az olyan globális objektumokkal való foglalkozás, mint a console, mellékhatásokkal járhat, ezért jobb, ha refaktoráljuk egy dedikált log függvényt használva.

Ez azt eredményezi, hogy nincs globális objektum a tesztkódunkban – édes 🤗

Mégis ugyanazt a hibát kapjuk. Let’s debug… boohoo nyilvánvalóan update nem szinkronizálódik – egy téves feltételezés, amit tettem 🙈 Mondom, a feltételezések elég gyakran veszélyesek – mégis időről időre beleesek 😢.

Szóval mit tehetünk? Sajnos úgy tűnik, hogy nincs nyilvános api, hogy bizonyos szinkronizálási műveleteket végezzenek, amelyeket egy tulajdonság frissítése vált ki.
Hozzunk létre egy problémát erre https://github.com/Polymer/lit-element/issues/643.

Előre úgy tűnik, hogy az egyetlen módja az, hogy egy privát api-ra támaszkodjunk. 🙈
Az értékszinkronizálást is át kellett helyeznünk a updated-ba, hogy minden dom renderelés után végrehajtódjon.

és itt a frissített teszt a naplózáshoz

wow, ez egy kicsit nehezebb volt, mint vártuk, de megcsináltuk 💪

SUMMARY:✔ 7 tests completedTOTAL: 7 SUCCESS

Tesztek futtatása Karma keretrendszer nélkül

A Karma keretrendszer erős és funkciógazdag, de néha talán szeretnénk lefaragni a tesztelési regimentünket. A jó dolog mindazzal, amit eddig javasoltunk, az, hogy csak a böngésző szabványos es moduljait használtuk, nincs szükség átfordításra, egyetlen kivétellel a csupasz modulok specifikálói.
Szóval csak egy test/index.html létrehozásával.

és megnyitjuk a owc-dev-server-on keresztül a chrome-ban, tökéletesen fog működni.
Megkaptunk mindent webpack vagy karma nélkül – édes 🤗

Do the Cross-Browser Thing

Most már elég jól érezzük magunkat a webkomponensünkkel. Leteszteltük és lefedett; már csak egy lépés van hátra – meg akarunk győződni róla, hogy minden böngészőben fut és tesztelve van.

Szóval futtassuk le

npm run test:bsSUMMARY:✔ 42 tests completedTOTAL: 42 SUCCESS

Igen, ez szépen működik! 🤗

Ha vannak sikertelen tesztek, akkor azokat kiírja az összefoglalóban azzal a konkrét böngészővel, ahol sikertelen volt.

Ha egy adott böngésző hibakeresésére van szükséged:

Szintén, ha be akarod állítani a tesztelésre kerülő böngészőt, akkor beállíthatod a karma.bs.config.js.

Ha például a Firefox ESR-t szeretnéd hozzáadni a listádhoz.

Vagy esetleg csak 2 meghatározott böngészőt szeretnél tesztelni?

Megjegyzés: Ez a webpack merge stratégiák cseréjét használja.

Gyors összefoglaló

  • A tesztelés minden projektnél fontos. Ügyeljen arra, hogy minél többet írjon.
  • Próbálja magasan tartani a kódlefedettséget, de ne feledje, hogy ez nem egy mágikus garancia, így nem kell mindig 100%-osnak lennie.
  • Hibakeresés a böngészőben a npm run test:watch segítségével. Legacy böngészők esetén használd a npm run test:legacy.watch-t.

Mi következik?

  • Futtasd a teszteket a CI-ben (tökéletesen működik a browserstackkel együtt). Lásd ajánlásainkat az automatizálásnál.

Kövess minket a Twitteren, vagy kövess engem a személyes Twitteremen.
Nézze meg a többi eszközt és ajánlást is az open-wc.org oldalon.

Köszönöm Pascalnak és Bennynek a visszajelzést és a segítséget, hogy a firkálmányaimat követhető történetté alakították.