„Jaki mamy coverage?” brzmi jak pytanie o jakość. W praktyce to pytanie o to, co w ogóle zdążyło się wykonać. Gdy testy pisze albo recenzuje model, procent z dashboardu bardzo chętnie staje się odpowiedzią. Ten tekst jest o tym, dlaczego to słaby walidator.
Sąsiedzi, nie zamienniki
Czas na zrozumienie zmiany, zanim w ogóle wybierzecie zakres, opisałem we wpisie o ukrytym podatku na PR. Pipeline, który krzyczy fałszywym alarmem, we wpisie o hałaśliwym CI. Osobno: zielony e2e, którego asercja była prawdziwa przed kliknięciem. Tu inny certyfikat, który ludzie i agenci lubią brać za werdykt: procent pokrycia.
Żadnego z tych sygnałów nie da się zastąpić jednym progiem „mamy 80%”.
Krótko o laboratorium
Prywatny stos demonstracyjny, klasyczny RealWorld rozcięty na SPA i API. Testy już były. Pokrycia nikt nie zbierał, nie publikował i nie bramkował.
Gdy to mierzyłem, w suite e2e na main była jedna ścieżka krytyczna: unikalna rejestracja, publikacja artykułu, kafelek na kanale globalnym. Nic więcej nie dodałem, żeby podbić procent. To jest punkt, nie wada zestawu.
Są z tego cztery raporty. Nie składam ich w jeden procent na cały stack.
Na PR-ze aplikacji jest tylko unit coverage. Jest na frontendzie, Coverlet na wycinku Mediatora w API. Te dwa checki nie startują Playwrighta. Specyfikacja Hurl zostaje osobnym jobem.
E2e ma osobny harness i flagę COVERAGE. Po merge PR-a aplikacji ta sama specyfikacja Node idzie z mutacją procesu API (dotnet-coverage) i zbiorem V8. Dwa razy dziennie, rano i wieczorem, ten sam Playwright idzie na niemutowanym dotnet run. To nie jest ten sam certyfikat.
Cztery liczby, których nie wolno dodać
Każdy z tych plików ma inny mianownik i inny proces. Nie dodawajcie ich do siebie. Slice Coverlet patrzy na 546 linii wycinka Mediatora. E2e API patrzy na 1110 linii żywego procesu Conduit. To nie jest ten sam ułamek z innym licznikiem.
| Raport | Co naprawdę zmierzono | Linie |
|---|---|---|
| Jest | Dwa testy-zaślepki (true / false). Mianownik: aplikacyjny src/ bez wygenerowanego klienta |
0% (0/727) |
| Slice Coverlet | Istniejące testy Mediator na InMemory, bez Kestrel i bez HTTP | 41% (224/546) |
| E2e API | Ta sama ścieżka z repo e2e, Chromium, dotnet-coverage na żywym procesie, potem filtr do Conduit |
46% (512/1110) |
| E2e V8 | JS z tej samej sesji, zmapowany do src/ z załadowanych chunków |
87% (1884/2155) w 43 plikach |
Gałęzie: 32% na slice, 21% na e2e API, 62% w tych 43 plikach V8.
CI czeka na GET /api/tags, zanim Playwright w ogóle wystartuje. To nie jest ta ścieżka, o którą chodzi w tekście. Kontroler tagów w raporcie e2e jest zielony dlatego, że host musiał odpowiedzieć, nie dlatego, że użytkownik klikał tagi.
Harness, nie druga kopia asercji
Nie duplikuję suite e2e w C#. Chromium prowadzi ta sama specyfikacja Node z repo e2e. Flaga COVERAGE decyduje tylko o uprzęży: albo zwykły dotnet run, albo proces owinięty kolektorem, plus V8 w tej samej sesji przeglądarki. Wcześniej pisałem o WebApplicationFactory i DI oraz o Blazorze z Playwright. Tu testy zostają w e2e. Mutacja jest po stronie procesu, nie po stronie drugiej kopii asercji.
Zanim te cztery pliki w ogóle dało się cytować, trzy artefakty kłamały. Coverlet na CI zapisał pusty Cobertura, a job był zielony. Nagłówek e2e API wciągnął FluentValidation i spadł do 15%, choć ścieżka szła tylko przez Conduit. V8 zbierane dopiero po ostatnim goto zostawiało boot i home: rejestracja i edytor znikały razem z leniwymi chunkami. Harness, który zostawiłem, obala pusty plik, filtruje Coberturę do pakietu Conduit i zbiera V8 przed każdą nawigacją. Procenty w tabeli są z tego przebiegu, nie z pierwszej próby.
Coverlet na wycinku Mediatora nie widzi kontrolerów. dotnet-coverage na e2e widzi HTTP, bo żądanie naprawdę weszło w pipeline. V8 nie widzi C#. C# nie widzi TSX. Jeśli ktoś zapyta model „jaki mamy coverage?”, a wy macie jeden badge, model dostanie pozwolenie, żeby te światy skleić.
Co ta ścieżka w ogóle nie załadowała
Niepokryte nie znaczy „złe”. Znaczy „ta ścieżka tam nie poszła”.
Po stronie API, przy 46% linii Conduit, zero trafień mają między innymi: logowanie, edycja profilu, follow, favorite, create/delete komentarza, edycja i usunięcie artykułu, profile. Lista komentarzy ma przy tym 90%, bo strona artykułu pobiera komentarze. Nikt komentarza nie napisał. Odczyt może być „pokryty”, a zachowania zapisu nie ma.
Po stronie UI Jest mówi 0%, bo testy-zaślepki nie importują aplikacji. V8 mówi 87% - ale tylko w 43 plikach, które webpack w ogóle włożył do załadowanych chunków. Dziewiętnaście plików z tego samego src/ nie wchodzi do raportu: ekran logowania, ekran 404, profil, wylogowanie, zapis ustawień, tworzenie i usuwanie komentarza, follow, favorite, edycja i usunięcie artykułu.
login.route.ts w raporcie V8 ma 100%. Nie logowaliśmy się formularzem. Router załadował moduł przy starcie aplikacji. settings.ui.tsx też ma 100%, bo rejestracja ląduje na ustawieniach. Nikt nic nie zapisał i nikt się nie wylogował. Procent nie rozróżnia „plik się zaimportował albo wyrenderował” od „użytkownik przeszedł ścieżkę”.
Co zrobi model przy bramce 80%
Dostanie 87% z V8 i zielone światło, mimo że login UI w raporcie nie istnieje. Albo dostanie 0% z Jest i zacznie „poprawiać pokrycie” tam, gdzie najłatwiej: asercje na kodzie, który i tak już się wykonał, wygenerowany klient, vendor, kolejne expect na tym samym kafelku.
Dlatego w tym laboratorium są artefakty i nie ma progu. Próg zamienia mapę wykonania w pieczątkę zaliczenia. Agent, którego poprosicie o recenzję testów, będzie optymalizował pieczątkę.
Nie musicie kupować produktu do pokrycia, żeby zobaczyć ten mechanizm. Wystarczy cztery pliki obok siebie i zakaz ich dodawania.
Zamknięcie
Zostawcie raporty osobno. Następna decyzja jest ludzka: czy ta ścieżka w ogóle zadaje pytanie, na którym wam zależy - i czego świadomie nie wykonała.
Pokrycie jest mapą tego, co się wykonało. Nie jest oceną, czy testy są dobre, i nie jest oceną, czy model dobrze je napisał albo zrecenzował.
