Czym jest code coverage?
Code coverage (pokrycie kodu) to metryka pokazująca, jaki procent kodu źródłowego jest wykonywany podczas testów. Coverage mierzy:
- Linie kodu – ile linii zostało wykonanych
- Funkcje – ile funkcji zostało wywołanych
- Klasy – ile klas zostało użytych
- Gałęzie (branches) – ile ścieżek wykonania zostało przetestowanych
Dlaczego coverage jest ważne?
- 📊 Wizualizacja – widzisz, które części kodu nie są testowane
- 🎯 Priorytetyzacja – wiesz, gdzie dodać testy
- 📈 Śledzenie postępów – monitorujesz poprawę jakości testów
- 🛡️ Minimalizacja ryzyka – kod bez testów to potencjalne błędy
Uwaga: 100% coverage nie oznacza, że kod jest idealnie przetestowany. Ważne jest jak testujesz, nie tylko ile. Niski coverage wskazuje jednak na braki w testach.
Jeśli dopiero zaczynasz z testowaniem, sprawdź najpierw wprowadzenie do unittest lub testowanie z pytest.
Instalacja coverage.py
Bash1pip install coverage
Lub z pytest-cov (integracja z pytest):
Bash1pip install pytest-cov
Weryfikacja instalacji:
Bash1coverage --version
Podstawowe użycie coverage.py
Uruchamianie testów z coverage
Najprostszy sposób – użyj coverage run:
Bash1 2 3 4 5 6 7 8# Z unittest coverage run -m unittest discover # Z pytest (bez pytest-cov) coverage run -m pytest # Z pytest-cov (zalecane) pytest --cov=src --cov-report=term
Wyświetlanie raportu
Po uruchomieniu testów wygeneruj raport:
Bash1 2 3 4 5 6 7 8# Raport w terminalu coverage report # Raport HTML (zalecane) coverage html # Raport XML (dla CI/CD) coverage xml
Raport w terminalu wygląda tak:
Bash1 2 3 4 5 6 7NameStmts MissCover --------------------------------------------- src/calculator.py 45589% src/utils.py30 1067% src/api.py60 2067% --------------------------------------------- TOTAL135 3574%
Integracja z pytest (pytest-cov)
pytest-cov to plugin pytest, który integruje coverage bezpośrednio z pytest:
Bash1 2 3 4 5 6 7 8 9 10 11 12 13 14# Podstawowe użycie pytest --cov=src # Z raportem w terminalu pytest --cov=src --cov-report=term # Z raportem HTML pytest --cov=src --cov-report=html # Z raportem XML pytest --cov=src --cov-report=xml # Z określonym progiem (fail jeśli poniżej) pytest --cov=src --cov-fail-under=80
Konfiguracja w pytest.ini
Zamiast dodawać opcje za każdym razem, możesz je skonfigurować w pytest.ini:
INI1 2 3 4 5 6[pytest] addopts = --cov=src --cov-report=term-missing --cov-report=html --cov-fail-under=80
Analiza raportów coverage
Raport terminalowy
Bash1coverage report
Przykładowy wynik:
Bash1 2 3 4 5 6 7NameStmts MissCover Missing ------------------------------------------------------- src/calculator.py 45589% 12-14, 28, 45 src/utils.py30 1067% 15-20, 35-38 src/api.py60 2067% 10-25, 50-55 ------------------------------------------------------- TOTAL135 3574%
Interpretacja:
- Stmts – liczba instrukcji (linii kodu)
- Miss – liczba niepokrytych instrukcji
- Cover – procent pokrycia
- Missing – numery linii, które nie były wykonane
Raport HTML
Bash1coverage html
Raport HTML otwiera się w przeglądarce (htmlcov/index.html) i pokazuje:
- Kolorowe oznaczenia – zielony (pokryte), żółty (częściowo), czerwony (niepokryte)
- Szczegółowe informacje o każdym pliku
- Interaktywne przeglądanie kodu źródłowego
To najlepszy sposób na wizualną analizę coverage!
Raport XML
Bash1coverage xml
Raport XML (coverage.xml) jest używany przez narzędzia CI/CD i analizy kodu (np. Codecov, Coveralls).
Konfiguracja coverage.py
Plik konfiguracyjny .coveragerc (lub pyproject.toml) pozwala skonfigurować coverage:
INI1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25# .coveragerc - podstawowa konfiguracja coverage [run] source = src omit = */tests/* */test_*.py */__pycache__/* */venv/* [report] exclude_lines = pragma: no cover def __repr__ raise AssertionError raise NotImplementedError if __name__ == .__main__.: if TYPE_CHECKING: @abstractmethod [html] directory = htmlcov title = Coverage Report [xml] output = coverage.xml
Lub w pyproject.toml:
TOML1 2 3 4 5 6 7 8 9 10 11 12 13 14[tool.coverage.run] source = ["src"] omit = [ "*/tests/*", "*/test_*.py", "*/__pycache__/*", ] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "def __repr__", "raise AssertionError", ]
Przykład praktyczny
Stwórzmy prosty projekt do demonstracji:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22# src/calculator.py def add(a, b): """Dodaje dwie liczby""" return a + b def subtract(a, b): """Odejmuje dwie liczby""" return a - b def divide(a, b): """Dzieli dwie liczby""" if b == 0: raise ValueError("Dzielenie przez zero!") return a / b def multiply(a, b): """Mnoży dwie liczby""" return a * b def power(a, b): """Potęguje a do b""" return a ** b
Testy:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16# tests/test_calculator.py import pytest from src.calculator import add, subtract, divide def test_add(): assert add(2, 3) == 5 def test_subtract(): assert subtract(5, 3) == 2 def test_divide(): assert divide(10, 2) == 5 def test_divide_by_zero(): with pytest.raises(ValueError): divide(10, 0)
Uruchomienie coverage:
Bash1pytest --cov=src --cov-report=term-missing
Wynik pokaże, że multiply i power nie są testowane:
Bash1 2 3 4 5NameStmts MissCover Missing ---------------------------------------------------- src/calculator.py16475% 11-12, 14-15 ---------------------------------------------------- TOTAL16475%
Wykluczanie kodu z coverage
Czasami część kodu nie powinna być liczona do coverage:
Wykluczanie całych plików
W .coveragerc:
INI1 2 3 4 5 6# .coveragerc - wykluczanie plików z coverage [run] omit = */tests/* */migrations/* */settings.py
Wykluczanie pojedynczych linii
Python1 2 3 4 5def experimental_feature(): if False:# pragma: no cover # Kod, który nie jest jeszcze używany pass return "work in progress"
Wykluczanie bloków kodu
Python1 2 3 4 5# pragma: no cover class LegacyCode: def old_method(self): # Stary kod, który nie wymaga testów pass
Wykluczanie w raportach
W .coveragerc:
INI1 2 3 4 5 6 7# .coveragerc - wykluczanie linii z raportów [report] exclude_lines = pragma: no cover def __repr__ if __debug__: if TYPE_CHECKING:
Ustawianie progów coverage
Próg dla całego projektu
Bash1pytest --cov=src --cov-fail-under=80
Jeśli coverage spadnie poniżej 80%, testy nie przejdą.
Próg dla poszczególnych modułów
W .coveragerc:
INI1 2 3 4 5 6# .coveragerc - ustawienie progów dla modułów [report] precision = 2 show_missing = True skip_covered = False fail_under = 80
Coverage w CI/CD
GitHub Actions
YAML1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39# .github/workflows/tests.yml name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: read steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' cache: 'pip' cache-dependency-path: requirements.txt - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest pytest-cov - name: Run tests with coverage run: | pytest --cov=src --cov-report=xml:coverage.xml --cov-report=term - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: files: ./coverage.xml fail_ci_if_error: true verbose: true env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
Więcej o automatyzacji testów w artykule o GitHub Actions.
GitLab CI
YAML1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20# .gitlab-ci.yml stages: - test test: stage: test image: python:3.11 script: - pip install --upgrade pip - pip install -r requirements.txt - pip install pytest pytest-cov - pytest --cov=src --cov-report=xml:coverage.xml coverage: '/TOTAL.*\s+(\d+%)$/' artifacts: reports: coverage_report: coverage_format: cobertura path: coverage.xml expire_in: 1 week
Analiza pokrycia – co sprawdzać?
1. Całkowite pokrycie
Bash1coverage report
Dąż do 80-90% coverage jako rozsądnego minimum. 100% jest często niemożliwe lub nieopłacalne.
2. Pokrycie gałęzi (branches)
Bash1pytest --cov=src --cov-branch
Pokrycie gałęzi sprawdza, czy wszystkie ścieżki w instrukcjach warunkowych są przetestowane:
Python1 2 3 4 5 6 7def process_value(value): if value > 0: return "positive" elif value < 0: return "negative" else:# Ta gałąź może nie być przetestowana! return "zero"
3. Pokrycie funkcji i klas
Bash1coverage report --show-missing
Sprawdź, czy wszystkie funkcje i klasy są wywołane w testach.
4. Trendy w czasie
Śledź, jak coverage zmienia się w czasie. Jeśli spada, to sygnał, że nowy kod nie jest testowany.
Najlepsze praktyki
1. Ustaw realistyczny próg
INI1 2 3# .coveragerc - ustawienie realistycznego progu coverage [report] fail_under = 80# 80% to dobry kompromis
Zbyt wysoki próg (np. 95%) może prowadzić do testów bez wartości dodanej.
2. Priorytetyzuj krytyczny kod
Nie wszystkie części kodu są równie ważne. Skup się na:
- Logice biznesowej
- Obsłudze błędów
- Funkcjach bezpieczeństwa
- Kompleksowych algorytmach
3. Ignoruj boilerplate
Wyklucz z coverage:
- Metody
__repr__,__str__ - Properties, które tylko zwracają wartości
- Kod tylko dla TYPE_CHECKING
4. Regularnie sprawdzaj raporty
Bash1 2 3# Dziennie podczas developmentu pytest --cov=src --cov-report=html open htmlcov/index.html
5. Używaj coverage do znajdowania luk
Raport HTML jest najlepszym narzędziem do identyfikacji niepokrytego kodu.
6. Nie gon za 100% coverage
Jakość testów > Pokrycie
Lepiej mieć 70% dobrych testów niż 100% słabych testów.
Typowe problemy i rozwiązania
Problem 1: Coverage pokazuje 0%
Przyczyna: Coverage nie wie, które pliki mierzyć.
Rozwiązanie: Ustaw source w .coveragerc:
INI1 2 3# .coveragerc - konfiguracja źródła dla coverage [run] source = src
Problem 2: Coverage pokazuje za mało
Przyczyna: Testy nie importują kodu lub używają mocków zamiast prawdziwego kodu.
Rozwiązanie: Sprawdź, czy testy rzeczywiście wywołują kod:
Python1 2 3 4 5 6 7# ✅ Dobrze - prawdziwy import from src.calculator import add result = add(2, 3) # ❌ Źle - mock zamiast prawdziwego kodu with patch('src.calculator.add'): pass
Problem 3: Coverage pokazuje nieprawidłowe wartości
Przyczyna: Cache coverage lub nieprawidłowa konfiguracja.
Rozwiązanie: Wyczyść cache:
Bash1 2coverage erase pytest --cov=src
Integracja z narzędziami zewnętrznymi
Codecov
Codecov to platforma do śledzenia coverage w czasie:
- Zarejestruj się na codecov.io
- Dodaj token do CI/CD
- Prześlij raport XML:
Bash1 2pytest --cov=src --cov-report=xml codecov -f coverage.xml
Coveralls
Coveralls to alternatywa dla Codecov:
Bash1 2pip install coveralls coveralls
SonarQube
SonarQube analizuje nie tylko coverage, ale też jakość kodu:
Bash1 2pytest --cov=src --cov-report=xml # SonarQube odczyta coverage.xml
Podsumowanie
Coverage to potężne narzędzie do mierzenia i poprawy jakości testów. Kluczowe punkty:
- ✅ Używaj
coverage.pylubpytest-covdo mierzenia pokrycia - ✅ Analizuj raporty HTML dla wizualnej identyfikacji luk
- ✅ Ustaw realistyczne progi (80-90%)
- ✅ Wykluczaj boilerplate i nieistotny kod
- ✅ Integruj coverage z CI/CD
- ✅ Pamiętaj: jakość testów > pokrycie
Coverage to narzędzie, które pokazuje gdzie są braki w testach, ale nie zastępuje myślenia o jak testować kod. Używaj coverage jako przewodnika, nie jako celu samego w sobie.
Co dalej?
Teraz, gdy umiesz mierzyć coverage, zastosuj to w praktyce:
- Testowanie z pytest – wykorzystaj
pytest-covdo automatycznego mierzenia coverage - Testy integracyjne API – sprawdź coverage w testach integracyjnych
- Automatyzacja testów w GitHub Actions – automatycznie mierz coverage w CI/CD
Pamiętaj: coverage to metryka, która pomaga, ale nie zastępuje dobrego myślenia o testach. Pisz wartościowe testy, a coverage będzie rosło naturalnie!



