Getting Started

Utiliser pytest pour les tests matériels

Apprenez à utiliser pytest pour automatiser les tests matériels et envoyer les résultats à TofuPilot pour un suivi centralisé et des analyses.

JJulien Buteau
beginner10 min de lecture14 mars 2026

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
FixturesConfigurer les instruments, alimentations, connexions
ParametrizeExécuter le même test sur plusieurs DUT ou configurations
MarkersÉtiqueter les tests par type (fonctionnel, sécurité, calibration)
AssertionsDé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

test_power_rails.py
import pytest
import pyvisa
from 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()

# Tests
def 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.

conftest.py
import pytest
from tofupilot import TofuPilotClient

class 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,
    )
test_board.py
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.35

def 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.85

def 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 <= 60

Option 2 : Plugin pytest

Écrivez un plugin pytest qui se branche sur les résultats de test.

conftest.py
import pytest
from tofupilot import TofuPilotClient

def 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

terminal
# Exécuter tous les tests matériels
pytest test_board.py -v

# Exécuter avec un numéro de série
pytest test_board.py --serial UNIT-5501

# Exécuter uniquement les tests de rails d'alimentation
pytest test_board.py -k "vcc" -v

# Exécuter avec des markers
pytest test_board.py -m "safety" -v

Organiser les tests matériels avec pytest

Utiliser les markers pour les catégories de tests

test_board.py
import pytest

@pytest.mark.safety
def 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.functional
def 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.calibration
def 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.5

Utiliser parametrize pour les tests multi-canaux

test_multi_channel.py
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) < tolerance

pytest vs. OpenHTF

FonctionnalitépytestOpenHTF
Courbe d'apprentissageFaible (la plupart des développeurs Python le connaissent)Moyenne (spécifique au matériel)
Fonctionnalités de test matérielGénéralisteConçu pour le matériel (phases, plugs, mesures)
Interface opérateurAucune intégréeInterface web intégrée
CommunautéTrès largePetite mais spécialisée
Intégration TofuPilotVia 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.

Plus de guides

Mettez ce guide en pratique