Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Test Coverage - mapowanie przetestowanych obszarów

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.

Odczytanie mapy

Raport zamawiasz jednym poleceniem:

1npm run test:cov

Ten 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.

Wyznaczenie progu

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.

Czego mapa nie pokazuje

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.

Podsumowanie

Mapa Imperium narysowana, białe plamy widoczne:

  • test coverage pokazuje, które fragmenty kodu wykonały się podczas testów,
  • raport zamawiasz przez
    npm run test:cov
    , czyli
    jest --coverage
    ,
  • statements i lines to pomiar najgrubszy, functions wyłapuje niewywołane funkcje,
  • branch coverage sprawdza ścieżki warunkowe - każde
    if
    w obu wariantach - i to on najlepiej mówi o jakości testów,
  • branch coverage jest zawsze niższy od statement coverage, więc jego próg ustawia się niżej,
  • coverageThreshold
    w konfiguracji Jest zamienia próg w twardy wymóg: spadek poniżej przerywa budowanie,
  • coverage mierzy wykonanie, nie poprawność - test bez
    expect
    da pełne pokrycie i nie sprawdzi niczego,
  • czytaj listę nieprzetestowanych linii, nie sam procent; zaczynaj od obsługi błędów i przypadków brzegowych.

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ł.

Przejdź do CodeWorlds