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()?
- Kapja az
innerHTML
árnyékgyökeret - Elveszi (tulajdonképpen, a böngésző elemzi – nincs szükség könyvtárra)
- Normalizálja (potenciálisan minden tag/tulajdonság a saját sorában)
- Megbontja és normalizálja a várt HTML karakterláncot
- Elküldi mindkét normalizált DOM karakterláncot a chai alapértelmezett compare függvényének
- 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 anpm 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.