Napisałeś już testy jednostkowe, integracyjne i E2E. Ale skąd wiesz, że nie zostawiłeś w kodzie ciemnego zaułka, do którego żaden test nigdy nie zajrzał?
Kartograf legionu ma na to sposób. Bierze mapę Imperium i zaznacza na niej każdą drogę, którą przeszedł zwiadowca. To, co zostanie białą plamą, nikt nie sprawdził. Test coverage - pokrycie kodu testami - jest właśnie taką mapą: uruchamiasz testy, a narzędzie zapisuje, które linie kodu naprawdę się wykonały.
Raport zamawiasz jednym poleceniem:
1npm run test:covTen skrypt to nic innego jak
jest --coverage - Jest wykonuje wszystkie testy, obserwując po drodze, których fragmentów kodu dotknął. Wynik wygląda tak:1Statements : 87.5% ( 175/200 )
2Branches : 83.3% ( 50/60 )
3Functions : 90.0% ( 45/50 )
4Lines : 88.0% ( 176/200 )Cztery liczby, cztery różne pytania o ten sam kod. Warto je rozdzielić, bo różnią się dokładnością.
Statements i Lines liczą wykonane instrukcje i linie - to najgrubszy pomiar, mówi tylko "tędy przechodziliśmy". Functions pyta, czy każda funkcja została choć raz wywołana; przydatne do wyłapania metod, o których wszyscy zapomnieli.
Najważniejsza jest trzecia liczba. Branches mierzy ścieżki warunkowe - czy każde
if sprawdzono zarówno wtedy, gdy warunek był prawdziwy, jak i wtedy, gdy fałszywy. Spójrz na tę funkcję:1function calculatePay(legionary: Legionary): number {
2 if (legionary.rank === 'CENTURION') {
3 return legionary.basePay * 2;
4 }
5 return legionary.basePay;
6}Jeden test z centurionem wykona obie linijki poza
return legionary.basePay - statement coverage podskoczy wysoko. Ale gałąź "to nie centurion" pozostanie niesprawdzona, a to właśnie w niej lubią siedzieć błędy. Dlatego branch coverage jest zawsze niższy od statement coverage i to on mówi prawdę o jakości testów.Sam raport niczego nie wymusza. Żeby pokrycie nie osuwało się z każdym tygodniem, ustawiamy próg w konfiguracji Jest:
1coverageThreshold: {
2 global: {
3 statements: 80,
4 branches: 75,
5 functions: 80,
6 lines: 80,
7 },
8},Od tej chwili spadek poniżej progu kończy się błędem - polecenie zwraca kod wyjścia różny od zera, więc zatrzyma też budowanie w CI. Zauważ, że próg dla
branches jest niższy niż dla reszty. To nie niedopatrzenie: gałęzi jest z natury trudniej dosięgnąć, więc realistyczny próg ustawia się niżej, żeby wymóg pozostał wykonalny, a nie stał się obrzędem obchodzonym przez wszystkich.Tu dochodzimy do rzeczy, o której łatwo zapomnieć, patrząc na ładne procenty. Coverage mierzy, czy kod się wykonał - nie czy zachował się poprawnie.
Ten test da stuprocentowe pokrycie i nie sprawdza niczego:
1it('oblicza żołd', () => {
2 calculatePay({ rank: 'CENTURION', basePay: 100 });
3});Funkcja została wywołana, więc kartograf zaznaczy drogę jako przebadaną. Ale nie ma tu ani jednego
expect - gdyby funkcja zwróciła ujemną liczbę albo rzuciła wyjątkiem, test nadal przeszedłby na zielono. Pokrycie mówi, gdzie zwiadowca był; nie mówi, czy patrzył.Stąd praktyczna zasada, @name: traktuj coverage jako wykrywacz białych plam, a nie jako ocenę. Niskie pokrycie to twardy sygnał, że czegoś nie sprawdzono - warto tam zajrzeć. Wysokie pokrycie nie dowodzi niczego samo z siebie. Pogoń za okrągłymi stu procentami kończy się zwykle testami pisanymi pod licznik: bez asercji, za to na getterach i plikach konfiguracyjnych.
Dlatego z raportu czytaj przede wszystkim listę nieprzetestowanych linii, a nie sam procent. To ona wskazuje ciemne zaułki, a wśród nich najpierw sprawdzaj obsługę błędów i przypadki brzegowe - bo tam trafiają najrzadziej uruchamiane, a najbardziej kosztowne gałęzie.
Mapa Imperium narysowana, białe plamy widoczne:
npm run test:cov, czyli jest --coverage,if w obu wariantach - i to on najlepiej mówi o jakości testów,coverageThreshold w konfiguracji Jest zamienia próg w twardy wymóg: spadek poniżej przerywa budowanie,expect da pełne pokrycie i nie sprawdzi niczego,W następnej lekcji zajmiemy się testami wydajnościowymi - sprawdzimy nie to, czy fort działa, ale ile naporu wytrzyma. A na razie zapamiętaj: coverage to mapa z zaznaczonymi drogami zwiadowców - białe plamy mówią, gdzie nikt nie był, ale zaznaczona droga nie dowodzi, że zwiadowca czegokolwiek na niej szukał.