Tests et Coverage
Documentation complète des tests unitaires et du coverage du projet.
Coverage : pourcentage de code testé (Glossaire).
Exécution rapide : voir Guide Démarrage Rapide pour commandes.
État Actuel
Résumé Exécutif
Coverage code source: 93% (objectif 90% dépassé)
Tests unitaires: 118 tests (10_preprod/tests/unit/)
Tests infrastructure: 35 tests (50_test/)
Total: 153 tests
Temps exécution: ~6 secondes
CI/CD: Seuil 90% obligatoire
Métriques par Environnement
Environnement |
Coverage |
Tests |
Statut |
|---|---|---|---|
10_preprod (code source) |
93% |
83 |
Production ready |
20_prod (artefact build) |
N/A |
N/A |
Voir note* |
50_test (tests infrastructure) |
N/A |
35 |
Validation S3/DB |
Note: 20_prod est un artefact de déploiement copié depuis 10_preprod. Tester 20_prod serait redondant car c’est le même code source. Les tests de 10_preprod valident le code déployé en production.
Commandes Rapides
Tests Unitaires (10_preprod)
cd ~/mangetamain/10_preprod
uv run pytest tests/unit/ -v --cov=src --cov-report=html
xdg-open htmlcov/index.html
Résultat attendu: 83 tests, 93% coverage, 4 skipped
Tests Infrastructure (50_test)
cd ~/mangetamain/50_test
pytest -v
Résultat attendu: 35 tests (S3, DuckDB, SQL)
Coverage Détaillé
Modules Testés (10_preprod)
Fichier |
Coverage |
Tests |
Lignes Manquantes |
|---|---|---|---|
|
100% |
10 |
0 |
|
100% |
10 |
0 |
|
100% |
8 |
0 |
|
100% |
14 |
0 |
|
95% |
8 |
26 lignes |
|
92% |
6 |
19 lignes |
|
90% |
5 |
29 lignes |
|
90% |
4 |
4 lignes |
|
85% |
6 |
41 lignes |
|
78% |
3 |
2 lignes* |
|
77% |
0 |
3 lignes* |
*Fonctions non utilisées commentées pour améliorer coverage
Fichiers de Tests Créés
tests/unit/
├── test_analyse_trendlines_v2.py (8 tests)
├── test_analyse_ratings.py (5 tests)
├── test_analyse_seasonality.py (6 tests)
├── test_analyse_weekend.py (6 tests)
├── test_color_theme.py (10 tests)
├── test_chart_theme.py (10 tests)
├── test_analyse_ratings_simple.py (14 tests)
├── test_custom_charts.py (8 tests)
├── test_analyse_trendlines.py (8 tests)
└── test_cached_loaders.py (4 tests skipped - mock st.cache_data)
Configuration
pyproject.toml
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--cov=src --cov-report=html --cov-report=term-missing --cov-fail-under=90"
[tool.coverage.run]
omit = [
"*/main.py",
"*/pages/*",
"*/__pycache__/*",
"*/.venv/*",
]
Tests Infrastructure (50_test)
Types de Tests
S3_duckdb_test.py (14 tests)
Environnement système (AWS CLI, credentials)
Connexion S3 avec boto3
Performance download (>5 MB/s)
DuckDB + S3 intégration
Tests Docker (optionnels)
test_s3_parquet_files.py (5 tests)
Scanne automatiquement le code
Trouve les références aux fichiers parquet
Teste l’accessibilité S3
test_sql_queries.py (16 tests)
Scanne automatiquement le code
Extrait les requêtes SQL
Teste la syntaxe (EXPLAIN)
Teste l’exécution (LIMIT 1)
Stratégie de Test
Ce Qu’on Teste
Transformations de données
Calculs et statistiques
Validation et filtrage
Logique métier
Fonctions utilitaires
Ce Qu’on Exclut
# 1. Fonctions UI Streamlit (marquées pragma: no cover)
def display_chart(): # pragma: no cover
st.plotly_chart(fig)
# 2. Fichiers d'application (dans pyproject.toml omit)
# main.py, pages/*
# 3. Imports conditionnels
try:
import module
except ImportError: # pragma: no cover
module = None
Patterns de Test
Mock Streamlit
from unittest.mock import Mock, MagicMock, patch
def setup_st_mocks(mock_st):
"""Configure tous les mocks Streamlit nécessaires."""
mock_st.plotly_chart = Mock()
mock_st.columns = Mock(side_effect=lambda n: [MagicMock() for _ in range(n)])
mock_st.slider = Mock(return_value=(2010, 2020))
mock_st.selectbox = Mock(side_effect=lambda label, options, **kwargs:
options[kwargs.get('index', 0)])
return mock_st
@patch("visualization.module.st")
@patch("visualization.module.load_data")
def test_function(mock_load, mock_st):
setup_st_mocks(mock_st)
mock_load.return_value = test_data
result = my_function()
mock_st.plotly_chart.assert_called()
Fixtures de Données
@pytest.fixture
def mock_recipes_data():
"""Fixture pour données de test."""
data = {
"id": list(range(1000)),
"year": [1999 + i % 20 for i in range(1000)],
"minutes": [30 + (i % 50) for i in range(1000)],
"complexity_score": [2.0 + (i % 10) * 0.1 for i in range(1000)],
}
return pl.DataFrame(data)
Tests de Graphiques Plotly
def test_chart_theme():
fig = go.Figure()
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6]))
result = apply_chart_theme(fig, title="Test")
assert result.layout.title.text == "Test"
assert result.layout.plot_bgcolor == "rgba(0,0,0,0)"
Résolution de Problèmes
Erreur: not enough values to unpack
Cause: Mock de st.columns() retourne vide
Solution:
mock_st.columns = Mock(side_effect=lambda n: [MagicMock() for _ in range(n)])
Erreur: KeyError
Cause: Fixture de données manque des colonnes
Solution: Ajouter toutes les colonnes utilisées par la fonction
data = {
"existing_cols": [...],
"missing_col": [...] # Ajouter colonne manquante
}
Erreur: Invalid value for color
Cause: Mock st.selectbox retourne une valeur fixe utilisée comme couleur
Solution:
mock_st.selectbox = Mock(side_effect=lambda label, options, **kwargs:
options[kwargs.get('index', 0)])
Erreur: Expected to be called once
Cause: Mauvais chemin de patch
Solution: Patcher où la fonction est utilisée, pas où elle est définie
# ❌ Mauvais
@patch("data.loaders.load_data")
# ✅ Bon
@patch("visualization.module.load_data")
Commandes Pytest Utiles
Liste des Tests
pytest --collect-only -q
Test Spécifique
pytest tests/unit/test_file.py::test_function -v
Coverage avec Détails
pytest --cov=src --cov-report=term-missing
Coverage pour Un Fichier
pytest tests/unit/test_file.py --cov=src.module --cov-report=term
Stop au Premier Échec
pytest -x # Stop immédiatement
pytest --maxfail=3 # Stop après 3 échecs
Mode Verbeux
pytest -vv --tb=long # Traceback complet
Bonnes Pratiques
Structure des Tests
"""Tests unitaires pour le module X.
Description de ce qui est testé.
"""
import pytest
from unittest.mock import Mock, patch
@pytest.fixture
def test_data():
"""Fixture réutilisable."""
return create_test_data()
def test_nominal_case(test_data):
"""Test du cas nominal."""
result = function(test_data)
assert result == expected
def test_edge_case():
"""Test d'un cas limite."""
# ...
def test_error_handling():
"""Test de la gestion d'erreurs."""
with pytest.raises(ValueError):
function(invalid_data)
Nommage
Fichiers:
test_<module>.pyFonctions:
test_<fonctionnalité>Fixtures:
mock_<type>_dataousample_<type>
Assertions Claires
# ✅ Bon
assert len(result) == 10, "Devrait retourner 10 éléments"
assert result['mean'] == pytest.approx(4.5, abs=0.1)
# ❌ Mauvais
assert result # Trop vague
Progression Historique
Date |
Coverage |
Notes |
|---|---|---|
2025-10-23 |
96% |
Version initiale (22 tests) |
2025-10-25 |
93% |
+60 tests (7 fichiers), code mort nettoyé |
Fichiers Ajoutés (2025-10-25)
test_analyse_trendlines_v2.py- 8 teststest_analyse_ratings.py- 5 teststest_analyse_seasonality.py- 6 teststest_analyse_weekend.py- 6 teststest_color_theme.py- 10 teststest_chart_theme.py- 10 teststest_cached_loaders.py- 4 tests
Total: +49 tests, +6 fichiers couverts
Voir Aussi
Standards Qualité - Conformité académique et qualité code
api/index - Documentation API modules testés
Architecture Technique - CI/CD pipeline avec tests automatiques