Cykl: Testowanie jednostkowe w Pythonie: Jak pisać szybkie i skuteczne testy z pytest · Część 6/7

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

Kacper Sieradziński
Kacper Sieradziński3 lipca 2025 · 4 min czytania
Streszczenie
  • → Czym jest code coverage?
  • → Dlaczego coverage jest ważne?
  • → Instalacja coverage.py
  • → Podstawowe użycie coverage.py
Coverage i mierzenie jakości testów w Pythonie – coverage.py i analiza pokrycia kodu

Pisanie testów to tylko połowa sukcesu. Równie ważne jest wiedzieć, ile kodu rzeczywiście testujesz i któ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.

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

Kurs Python dla początkujących — PyStart

Materiały VOD + ćwiczenia

Kurs

Kurs Python dla początkujących — PyStart

Zacznij programować w Pythonie! Idealne dla osób bez doświadczenia. Praktyczne zadania, projekty i wsparcie społeczności.

699 zł
Rozpocznij naukę

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 multiply i power 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

Newsletter · co środę

Python co tydzień — newsletter dla programistów

Otrzymuj codzienne ćwiczenia, ciekawostki z ekosystemu Pythona i wskazówki do rozmów rekrutacyjnych.

Problem 1: Coverage pokazuje 0%

Przyczyna: Coverage nie wie, które pliki mierzyć.

Rozwiązanie: Ustaw source w .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!

Część 7 z 7

Automatyzacja testów w GitHub Actions – CI/CD dla projektów Python

druga lekcja cyklu „Testowanie jednostkowe w Pythonie: Jak pisać szybkie i skuteczne testy z pytest"

Czytaj kolejny →