"""
Script d'exécution des tests avec génération de rapports XML pour Jenkins.
Ce script permet d'exécuter automatiquement tous les tests unitaires du projet et de générer
un rapport au format XML compatible avec Jenkins pour l'intégration continue.
"""
import unittest
import sys
import os
import xml.etree.ElementTree as ET
from datetime import datetime
import logging
# Configuration du système de logging pour tracer l'exécution des tests
# Les logs sont affichés dans la console et sauvegardés dans un fichier
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout), # Affichage console
logging.FileHandler( # Sauvegarde dans un fichier
os.path.join(os.path.dirname(__file__), 'reports',
'test_execution.log'))
])
logger = logging.getLogger(__name__)
# Ajout du répertoire source au PYTHONPATH pour permettre l'import des modules à tester
src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))
sys.path.insert(0, src_path)
logger.info(f"Ajout du chemin source au PYTHONPATH: {src_path}")
[docs]
class XMLTestResult(unittest.TestResult):
"""Classe personnalisée pour générer des résultats de test au format XML.
Cette classe étend unittest.TestResult pour capturer les résultats des tests
et les formater en XML compatible avec Jenkins. Elle trace également le temps
d'exécution de chaque test et leur statut (succès, échec, erreur).
"""
[docs]
def __init__(self, *args, **kwargs):
"""Initialise le collecteur de résultats de tests."""
super().__init__(*args, **kwargs)
self.test_cases = [] # Liste pour stocker les résultats de chaque test
logger.info("Initialisation du XMLTestResult")
[docs]
def startTest(self, test):
"""Appelé au début de chaque test pour enregistrer son temps de démarrage."""
self.start_time = datetime.now()
logger.info(f"Démarrage du test: {test.id()}")
super().startTest(test)
[docs]
def addSuccess(self, test):
"""Enregistre un test réussi avec sa durée d'exécution."""
duration = (datetime.now() - self.start_time).total_seconds()
logger.info(f"Test réussi: {test.id()} (durée: {duration:.3f}s)")
self.test_cases.append({
'name': test.id(),
'status': 'success',
'time': duration
})
super().addSuccess(test)
[docs]
def addError(self, test, err):
"""Enregistre une erreur survenue pendant l'exécution d'un test."""
duration = (datetime.now() - self.start_time).total_seconds()
logger.error(f"Erreur dans le test {test.id()}: {str(err[1])}")
logger.error(f"Traceback: {str(err[2])}")
self.test_cases.append({
'name': test.id(),
'status': 'error',
'message': str(err[1]),
'time': duration
})
super().addError(test, err)
[docs]
def addFailure(self, test, err):
"""Enregistre un échec de test (assertion non validée)."""
duration = (datetime.now() - self.start_time).total_seconds()
logger.warning(f"Échec du test {test.id()}: {str(err[1])}")
self.test_cases.append({
'name': test.id(),
'status': 'failure',
'message': str(err[1]),
'time': duration
})
super().addFailure(test, err)
[docs]
def generate_xml(self):
"""Génère le rapport XML au format attendu par Jenkins.
Crée une structure XML contenant tous les résultats des tests
avec leurs statuts, durées et messages d'erreur éventuels.
"""
logger.info("Génération du rapport XML")
root = ET.Element('testsuites')
testsuite = ET.SubElement(root,
'testsuite',
name='api_ihm_tests',
tests=str(self.testsRun),
errors=str(len(self.errors)),
failures=str(len(self.failures)))
# Ajout de chaque cas de test au rapport
for case in self.test_cases:
testcase = ET.SubElement(testsuite,
'testcase',
name=case['name'],
time=str(case['time']))
if case['status'] in ['error', 'failure']:
failure = ET.SubElement(testcase, case['status'])
failure.text = case.get('message', '')
logger.info(
f"Rapport XML généré avec {self.testsRun} tests, "
f"{len(self.errors)} erreurs et {len(self.failures)} échecs")
return ET.ElementTree(root)
[docs]
def run_tests():
"""Exécute la suite de tests et génère le rapport.
Cette fonction :
1. Crée le répertoire des rapports si nécessaire
2. Découvre automatiquement tous les tests du projet
3. Exécute les tests et collecte les résultats
4. Génère et sauvegarde le rapport XML
5. Affiche un résumé des résultats
Returns:
int: 0 si tous les tests sont réussis, 1 sinon
"""
try:
# Création du répertoire pour les rapports de test
reports_dir = os.path.join(os.path.dirname(__file__), 'reports')
os.makedirs(reports_dir, exist_ok=True)
logger.info(f"Répertoire des rapports créé: {reports_dir}")
# Découverte automatique des tests (fichiers commençant par 'test_')
loader = unittest.TestLoader()
logger.info("Recherche des tests...")
suite = loader.discover(start_dir=os.path.dirname(__file__),
pattern='test_*.py')
logger.info(f"Tests trouvés: {suite.countTestCases()} cas de test")
# Exécution des tests avec notre collecteur de résultats personnalisé
logger.info("Démarrage de l'exécution des tests")
result = XMLTestResult()
suite.run(result)
# Génération et sauvegarde du rapport au format XML
xml_report = result.generate_xml()
report_path = os.path.join(reports_dir, 'test-results.xml')
xml_report.write(report_path, encoding='utf-8', xml_declaration=True)
logger.info(f"Rapport XML sauvegardé: {report_path}")
# Affichage du résumé des résultats
success = result.wasSuccessful()
logger.info("=== Résumé des tests ===")
logger.info(f"Tests exécutés: {result.testsRun}")
logger.info(
f"Succès: {result.testsRun - len(result.errors) - len(result.failures)}"
)
logger.info(f"Erreurs: {len(result.errors)}")
logger.info(f"Échecs: {len(result.failures)}")
logger.info(f"Statut global: {'Succès' if success else 'Échec'}")
return 0 if success else 1
except Exception as e:
logger.error(f"Erreur lors de l'exécution des tests: {str(e)}",
exc_info=True)
return 1
if __name__ == '__main__':
try:
logger.info("=== Démarrage de la suite de tests ===")
exit_code = run_tests()
logger.info(
f"=== Fin de la suite de tests (code de sortie: {exit_code}) ===")
sys.exit(exit_code)
except KeyboardInterrupt:
# Gestion de l'interruption manuelle (Ctrl+C)
logger.warning("Interruption manuelle des tests")
sys.exit(130)
except Exception as e:
# Gestion des erreurs inattendues
logger.error(f"Erreur fatale: {str(e)}", exc_info=True)
sys.exit(1)