Python : Pytest
· 6 min read
Pytest est le framework de testing le plus populaire en Python. Il offre une syntaxe simple, des fonctionnalités puissantes et une grande flexibilité pour écrire des tests de qualité.
Installation et Configuration
Installer pytest
pip install pytest
Ou avec uv :
uv pip install pytest
Structure basique
project/
├── src/
│ └── mymodule.py
├── tests/
│ ├── __init__.py
│ ├── test_mymodule.py
│ └── conftest.py
└── pytest.ini
Tests Simples
Première fonction
# src/calculator.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b
Écrire des tests
# tests/test_calculator.py
from src.calculator import add, multiply
def test_add():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, 1) == 0
def test_multiply():
assert multiply(3, 4) == 12
Lancer les tests
# Tous les tests
pytest
# Fichier spécifique
pytest tests/test_calculator.py
# Test spécifique
pytest tests/test_calculator.py::test_add
# Verbose
pytest -v
# Avec couverture
pytest --cov=src
Assertions
Assertions de base
def test_assertions():
# Égalité
assert 1 == 1
# Inégalité
assert 1 != 2
# Comparaisons
assert 5 > 3
assert 3 <= 3
# Vérité
assert True
assert not False
# Contenance
assert "hello" in "hello world"
assert 1 in [1, 2, 3]
Messages personnalisés
def test_with_message():
result = add(2, 3)
assert result == 5, f"Expected 5, got {result}"
Assertions sur les exceptions
import pytest
def divide(a, b):
if b == 0:
raise ValueError("Division by zero")
return a / b
def test_division_by_zero():
with pytest.raises(ValueError):
divide(10, 0)
# Vérifier le message
with pytest.raises(ValueError, match="Division by zero"):
divide(10, 0)
Fixtures
Les fixtures fournissent des données/ressources réutilisables pour les tests.
Fixture simple
import pytest
@pytest.fixture
def sample_data():
return {"name": "John", "age": 30}
def test_with_fixture(sample_data):
assert sample_data["name"] == "John"
assert sample_data["age"] == 30
Fixture avec setup/teardown
@pytest.fixture
def database():
# Setup
db = create_connection()
yield db # Le test s'exécute ici
# Teardown
db.close()
def test_database_query(database):
result = database.query("SELECT * FROM users")
assert len(result) > 0
Fixtures conftest.py (partage)
Les fixtures dans conftest.py sont disponibles pour tous les tests du projet.
# tests/conftest.py
import pytest
@pytest.fixture(scope="session")
def app():
"""Partagée pour toute la session de test"""
return create_app()
@pytest.fixture(scope="module")
def client(app):
"""Partagée pour tout le module"""
return app.test_client()
@pytest.fixture(scope="function")
def temp_file():
"""Nouvelle instance pour chaque test (par défaut)"""
file = open("temp.txt", "w")
yield file
file.close()
Parametrization
Tester une fonction avec plusieurs jeux de données.
@pytest.mark.parametrize
@pytest.mark.parametrize("input,expected", [
(2, 4),
(3, 9),
(5, 25),
(-2, 4),
])
def test_square(input, expected):
assert input ** 2 == expected
Parametrization multiple
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
(10, -5, 5),
])
def test_add_multiple(a, b, expected):
assert add(a, b) == expected
Combinaison avec fixtures
@pytest.mark.parametrize("username", ["alice", "bob", "charlie"])
def test_user_creation(client, username):
response = client.post("/users", json={"name": username})
assert response.status_code == 201
Mocking avec unittest.mock
Remplacer des dépendances pour isoler le code testé.
Mock simple
from unittest.mock import patch, MagicMock
import requests
def fetch_data(url):
response = requests.get(url)
return response.json()
@patch('requests.get')
def test_fetch_data(mock_get):
mock_get.return_value.json.return_value = {"name": "John"}
result = fetch_data("http://example.com/api/user")
assert result["name"] == "John"
Mock avec side_effect
@patch('requests.get')
def test_fetch_data_error(mock_get):
mock_get.side_effect = requests.ConnectionError("Connection failed")
with pytest.raises(requests.ConnectionError):
fetch_data("http://example.com/api/user")