Coverage i mierzenie jakości testów w Pythonie – coverage.py i analiza pokrycia kodu

Kacper Sieradziński
Kacper Sieradziński
3 lipca 2025Edukacja5 min czytania

Pisanie testów to tylko połowa sukcesu. Równie ważne jest wiedzieć, ile kodu rzeczywiście testujeszktóre fragmenty nie są pokryte testami. Narzędzie coverage.py pozwala mierzyć pokrycie kodu testami (code coverage) i identyfikować luki w testach. W tym artykule nauczysz się używać coverage.py, interpretować raporty pokrycia, ustawiać progi jakości i wykorzystywać coverage do systematycznego podnoszenia jakości testów w Twoich projektach.

Obraz główny Coverage i mierzenie jakości testów w Pythonie – coverage.py i analiza pokrycia kodu

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

Bash
1 pip install coverage

Lub z pytest-cov (integracja z pytest):

Bash
1 pip install pytest-cov

Weryfikacja instalacji:

Bash
1 coverage --version

Podstawowe użycie coverage.py

Uruchamianie testów z coverage

Najprostszy sposób – użyj coverage run:

Bash
1 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:

Bash
1 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:

Bash
1 2 3 4 5 6 7 NameStmts 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:

Bash
1 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:

INI
1 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

Bash
1 coverage report

Przykładowy wynik:

Bash
1 2 3 4 5 6 7 NameStmts 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

Bash
1 coverage 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

Bash
1 coverage 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:

INI
1 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:

TOML
1 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:

Python
1 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:

Python
1 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:

Bash
1 pytest --cov=src --cov-report=term-missing

Wynik pokaże, że multiplypower nie są testowane:

Bash
1 2 3 4 5 NameStmts 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:

INI
1 2 3 4 5 6 # .coveragerc - wykluczanie plików z coverage [run] omit = */tests/* */migrations/* */settings.py

Wykluczanie pojedynczych linii

Python
1 2 3 4 5 def experimental_feature(): if False:# pragma: no cover # Kod, który nie jest jeszcze używany pass return "work in progress"

Wykluczanie bloków kodu

Python
1 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:

INI
1 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

Bash
1 pytest --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:

INI
1 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

YAML
1 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

YAML
1 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

Bash
1 coverage report

Dąż do 80-90% coverage jako rozsądnego minimum. 100% jest często niemożliwe lub nieopłacalne.

2. Pokrycie gałęzi (branches)

Bash
1 pytest --cov=src --cov-branch

Pokrycie gałęzi sprawdza, czy wszystkie ścieżki w instrukcjach warunkowych są przetestowane:

Python
1 2 3 4 5 6 7 def 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

Bash
1 coverage 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

INI
1 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

Bash
1 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.coveragerc:

INI
1 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:

Python
1 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:

Bash
1 2 coverage erase pytest --cov=src

Integracja z narzędziami zewnętrznymi

Codecov

Codecov to platforma do śledzenia coverage w czasie:

  1. Zarejestruj się na codecov.io
  2. Dodaj token do CI/CD
  3. Prześlij raport XML:
Bash
1 2 pytest --cov=src --cov-report=xml codecov -f coverage.xml

Coveralls

Coveralls to alternatywa dla Codecov:

Bash
1 2 pip install coveralls coveralls

SonarQube

SonarQube analizuje nie tylko coverage, ale też jakość kodu:

Bash
1 2 pytest --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.py lub pytest-cov do 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:

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!