Comment utiliser pytest pour les tests matériels avec TofuPilot
pytest est le framework de test Python le plus populaire. Vous le connaissez déjà pour les tests logiciels. Vous pouvez aussi l'utiliser pour les tests matériels. Ce guide montre comment structurer les tests matériels avec pytest et envoyer les résultats à TofuPilot.
Pourquoi pytest pour le matériel
| Fonctionnalité | Comment elle aide les tests matériels |
|---|---|
| Fixtures | Configurer les instruments, alimentations, connexions |
| Parametrize | Exécuter le même test sur plusieurs DUT ou configurations |
| Markers | Étiqueter les tests par type (fonctionnel, sécurité, calibration) |
| Assertions | Définir les critères de réussite/échec pour les mesures |
| Plugins | Étendre avec du reporting personnalisé, exécution parallèle |
pytest vous fournit le lanceur de tests. TofuPilot vous fournit la plateforme de données. Ensemble, ils remplacent les scripts ad-hoc et les fichiers Excel.
Test matériel de base avec pytest
import pytestimport pyvisafrom tofupilot import TofuPilotClient# Fixtures pour la configuration des instruments@pytest.fixture(scope="session")def rm(): return pyvisa.ResourceManager()@pytest.fixture(scope="session")def dmm(rm): inst = rm.open_resource("TCPIP::192.168.1.10::INSTR") yield inst inst.close()@pytest.fixture(scope="session")def psu(rm): inst = rm.open_resource("TCPIP::192.168.1.11::INSTR") inst.write("OUTP ON") yield inst inst.write("OUTP OFF") inst.close()@pytest.fixture(scope="session")def tofupilot(): return TofuPilotClient()# Testsdef test_vcc_3v3(dmm, tofupilot): voltage = float(dmm.query("MEAS:VOLT:DC?")) assert 3.25 <= voltage <= 3.35, f"Rail 3,3 V hors spécification : {voltage} V"def test_vcc_1v8(dmm, tofupilot): voltage = float(dmm.query("MEAS:VOLT:DC?")) assert 1.75 <= voltage <= 1.85, f"Rail 1,8 V hors spécification : {voltage} V"def test_idle_current(dmm, tofupilot): current = float(dmm.query("MEAS:CURR:DC?")) * 1000 # mA assert 30 <= current <= 60, f"Courant de repos hors spécification : {current} mA"Envoi des résultats pytest à TofuPilot
Option 1 : Envoi dans une fixture (recommandé)
Collectez les mesures pendant les tests et envoyez-les à la fin.
import pytestfrom tofupilot import TofuPilotClientclass TestCollector: """Collecte les mesures pendant une session de test.""" def __init__(self): self.measurements = [] self.steps = [] self.all_passed = True def add_measurement(self, step_name, name, value, unit, limit_low=None, limit_high=None): passed = True if limit_low is not None and value < limit_low: passed = False if limit_high is not None and value > limit_high: passed = False if not passed: self.all_passed = False # Trouver ou créer l'étape step = next((s for s in self.steps if s["name"] == step_name), None) if not step: step = {"name": step_name, "step_type": "measurement", "status": True, "measurements": []} self.steps.append(step) measurement = {"name": name, "value": value, "unit": unit} if limit_low is not None: measurement["limit_low"] = limit_low if limit_high is not None: measurement["limit_high"] = limit_high step["measurements"].append(measurement) if not passed: step["status"] = False@pytest.fixture(scope="session")def collector(): return TestCollector()@pytest.fixture(scope="session", autouse=True)def upload_results(collector): yield # Exécuter tous les tests # Envoyer après la fin de tous les tests client = TofuPilotClient() serial = input("Entrez le numéro de série du DUT : ") if not hasattr(collector, "serial") else collector.serial client.create_run( procedure_id="PYTEST-BOARD-FUNCTIONAL", unit_under_test={"serial_number": serial}, run_passed=collector.all_passed, steps=collector.steps, )def test_vcc_3v3(dmm, collector): voltage = float(dmm.query("MEAS:VOLT:DC?")) collector.add_measurement("Power Rails", "vcc_3v3", voltage, "V", limit_low=3.25, limit_high=3.35) assert 3.25 <= voltage <= 3.35def test_vcc_1v8(dmm, collector): voltage = float(dmm.query("MEAS:VOLT:DC?")) collector.add_measurement("Power Rails", "vcc_1v8", voltage, "V", limit_low=1.75, limit_high=1.85) assert 1.75 <= voltage <= 1.85def test_idle_current(dmm, collector): current = float(dmm.query("MEAS:CURR:DC?")) * 1000 collector.add_measurement("Current Draw", "idle_current_ma", current, "mA", limit_low=30, limit_high=60) assert 30 <= current <= 60Option 2 : Plugin pytest
Écrivez un plugin pytest qui se branche sur les résultats de test.
import pytestfrom tofupilot import TofuPilotClientdef pytest_sessionfinish(session, exitstatus): """Envoie les résultats après la fin de tous les tests.""" client = TofuPilotClient() passed = exitstatus == 0 # Collecter les résultats de la session steps = [] for item in session.items: report = item.stash.get("report", None) if report: steps.append({ "name": item.name, "step_type": "measurement", "status": report.passed, "measurements": item.stash.get("measurements", []), }) client.create_run( procedure_id="PYTEST-FUNCTIONAL", unit_under_test={"serial_number": session.config.getoption("--serial", "UNKNOWN")}, run_passed=passed, steps=steps, )Exécution des tests
# Exécuter tous les tests matérielspytest test_board.py -v# Exécuter avec un numéro de sériepytest test_board.py --serial UNIT-5501# Exécuter uniquement les tests de rails d'alimentationpytest test_board.py -k "vcc" -v# Exécuter avec des markerspytest test_board.py -m "safety" -vOrganiser les tests matériels avec pytest
Utiliser les markers pour les catégories de tests
import pytest@pytest.mark.safetydef test_hipot(hipot_tester, collector): """Test de sécurité - doit réussir pour chaque unité.""" leakage = hipot_tester.run_test(1500) collector.add_measurement("Safety", "hipot_leakage_ma", leakage, "mA", limit_high=5.0) assert leakage < 5.0@pytest.mark.functionaldef test_communication(dut, collector): """Test fonctionnel - vérifie le fonctionnement de base.""" response = dut.ping() collector.add_measurement("Communication", "uart_ping", 1 if response else 0, "bool", limit_low=1) assert response@pytest.mark.calibrationdef test_adc_accuracy(dut, collector): """Test de calibration - vérifie la précision des mesures.""" error = dut.measure_adc_error() collector.add_measurement("Calibration", "adc_error_pct", error, "%", limit_high=0.5) assert error < 0.5Utiliser parametrize pour les tests multi-canaux
import pytest@pytest.mark.parametrize("channel,expected_v,tolerance", [ (1, 3.3, 0.05), (2, 1.8, 0.05), (3, 5.0, 0.10), (4, 12.0, 0.20),])def test_voltage_rail(dmm, collector, channel, expected_v, tolerance): voltage = dmm.measure_channel(channel) collector.add_measurement( "Power Rails", f"rail_ch{channel}_v", voltage, "V", limit_low=expected_v - tolerance, limit_high=expected_v + tolerance, ) assert abs(voltage - expected_v) < tolerancepytest vs. OpenHTF
| Fonctionnalité | pytest | OpenHTF |
|---|---|---|
| Courbe d'apprentissage | Faible (la plupart des développeurs Python le connaissent) | Moyenne (spécifique au matériel) |
| Fonctionnalités de test matériel | Généraliste | Conçu pour le matériel (phases, plugs, mesures) |
| Interface opérateur | Aucune intégrée | Interface web intégrée |
| Communauté | Très large | Petite mais spécialisée |
| Intégration TofuPilot | Via du code personnalisé | Callback natif |
Utilisez pytest quand votre équipe le connaît déjà et que vous voulez quelque chose de fonctionnel rapidement. Utilisez OpenHTF quand vous avez besoin du framework complet de test matériel avec interface opérateur et phases structurées.
Les deux envoient les données à TofuPilot de la même manière. Les données dans TofuPilot sont identiques quel que soit le framework qui les a générées.