Add driver unit tests

This commit is contained in:
2025-07-10 15:57:02 +00:00
parent 4db50421b3
commit 76d81b21e6

346
tests/unit/test_drivers.py Normal file
View File

@@ -0,0 +1,346 @@
"""Unit tests for instrument drivers.
Tests SCPI command formatting and driver functionality using mock transports.
"""
from unittest.mock import MagicMock
import pytest
from py_dvt_ate.instruments.drivers.base import BaseDriver
from py_dvt_ate.instruments.drivers.chamber import ThermalChamberDriver
from py_dvt_ate.instruments.drivers.multimeter import MultimeterDriver
from py_dvt_ate.instruments.drivers.power_supply import PowerSupplyDriver
@pytest.fixture
def mock_transport():
"""Create a mock transport for testing."""
transport = MagicMock()
transport.is_connected = True
return transport
class TestBaseDriver:
"""Tests for BaseDriver base class."""
def test_connect(self, mock_transport):
"""Test connection establishment."""
driver = BaseDriver(mock_transport)
driver.connect()
mock_transport.connect.assert_called_once()
def test_disconnect(self, mock_transport):
"""Test disconnection."""
driver = BaseDriver(mock_transport)
driver.disconnect()
mock_transport.disconnect.assert_called_once()
def test_is_connected(self, mock_transport):
"""Test connection status check."""
driver = BaseDriver(mock_transport)
assert driver.is_connected is True
def test_write(self, mock_transport):
"""Test SCPI command write."""
driver = BaseDriver(mock_transport)
driver.write("VOLT 3.3")
mock_transport.write.assert_called_once_with("VOLT 3.3")
def test_query(self, mock_transport):
"""Test SCPI query."""
mock_transport.query.return_value = "3.300"
driver = BaseDriver(mock_transport)
result = driver.query("VOLT?")
assert result == "3.300"
mock_transport.query.assert_called_once_with("VOLT?", None)
def test_query_float(self, mock_transport):
"""Test SCPI query with float parsing."""
mock_transport.query.return_value = "3.300"
driver = BaseDriver(mock_transport)
result = driver.query_float("VOLT?")
assert result == 3.3
assert isinstance(result, float)
def test_query_float_invalid(self, mock_transport):
"""Test SCPI query with invalid float response."""
mock_transport.query.return_value = "INVALID"
driver = BaseDriver(mock_transport)
with pytest.raises(ValueError, match="Cannot parse 'INVALID' as float"):
driver.query_float("VOLT?")
def test_query_int(self, mock_transport):
"""Test SCPI query with integer parsing."""
mock_transport.query.return_value = "42"
driver = BaseDriver(mock_transport)
result = driver.query_int("COUNT?")
assert result == 42
assert isinstance(result, int)
def test_query_int_invalid(self, mock_transport):
"""Test SCPI query with invalid integer response."""
mock_transport.query.return_value = "3.14"
driver = BaseDriver(mock_transport)
with pytest.raises(ValueError, match="Cannot parse '3.14' as int"):
driver.query_int("COUNT?")
def test_query_bool_true_variants(self, mock_transport):
"""Test SCPI query with boolean parsing - true variants."""
driver = BaseDriver(mock_transport)
for value in ["1", "ON", "TRUE", "on", "true"]:
mock_transport.query.return_value = value
result = driver.query_bool("OUTP?")
assert result is True
def test_query_bool_false_variants(self, mock_transport):
"""Test SCPI query with boolean parsing - false variants."""
driver = BaseDriver(mock_transport)
for value in ["0", "OFF", "FALSE", "off", "false"]:
mock_transport.query.return_value = value
result = driver.query_bool("OUTP?")
assert result is False
def test_query_bool_invalid(self, mock_transport):
"""Test SCPI query with invalid boolean response."""
mock_transport.query.return_value = "MAYBE"
driver = BaseDriver(mock_transport)
with pytest.raises(ValueError, match="Cannot parse 'MAYBE' as bool"):
driver.query_bool("OUTP?")
def test_identify(self, mock_transport):
"""Test instrument identification query."""
mock_transport.query.return_value = "Manufacturer,Model,SN123,1.0.0"
driver = BaseDriver(mock_transport)
result = driver.identify()
assert result == "Manufacturer,Model,SN123,1.0.0"
mock_transport.query.assert_called_once_with("*IDN?", None)
def test_reset(self, mock_transport):
"""Test instrument reset command."""
driver = BaseDriver(mock_transport)
driver.reset()
mock_transport.write.assert_called_once_with("*RST")
def test_clear_status(self, mock_transport):
"""Test clear status command."""
driver = BaseDriver(mock_transport)
driver.clear_status()
mock_transport.write.assert_called_once_with("*CLS")
def test_operation_complete(self, mock_transport):
"""Test operation complete query."""
mock_transport.query.return_value = "1"
driver = BaseDriver(mock_transport)
result = driver.operation_complete()
assert result is True
mock_transport.query.assert_called_once_with("*OPC?", None)
class TestThermalChamberDriver:
"""Tests for ThermalChamberDriver."""
def test_set_temperature(self, mock_transport):
"""Test temperature setpoint command."""
driver = ThermalChamberDriver(mock_transport)
driver.set_temperature(85.0)
mock_transport.write.assert_called_once_with("TEMP:SETPOINT 85.00")
def test_get_temperature(self, mock_transport):
"""Test temperature measurement query."""
mock_transport.query.return_value = "25.50"
driver = ThermalChamberDriver(mock_transport)
temp = driver.get_temperature()
assert temp == 25.5
mock_transport.query.assert_called_once_with("TEMP:ACTUAL?", None)
def test_get_setpoint(self, mock_transport):
"""Test setpoint query."""
mock_transport.query.return_value = "85.00"
driver = ThermalChamberDriver(mock_transport)
setpoint = driver.get_setpoint()
assert setpoint == 85.0
mock_transport.query.assert_called_once_with("TEMP:SETPOINT?", None)
def test_is_stable_true(self, mock_transport):
"""Test stability check - stable."""
mock_transport.query.return_value = "1"
driver = ThermalChamberDriver(mock_transport)
assert driver.is_stable() is True
def test_is_stable_false(self, mock_transport):
"""Test stability check - not stable."""
mock_transport.query.return_value = "0"
driver = ThermalChamberDriver(mock_transport)
assert driver.is_stable() is False
def test_wait_until_stable_immediate(self, mock_transport):
"""Test wait for stability - already stable."""
mock_transport.query.return_value = "1"
driver = ThermalChamberDriver(mock_transport)
result = driver.wait_until_stable(timeout=5.0, poll_interval=0.1)
assert result is True
def test_wait_until_stable_timeout(self, mock_transport):
"""Test wait for stability - timeout."""
mock_transport.query.return_value = "0" # Never becomes stable
driver = ThermalChamberDriver(mock_transport)
result = driver.wait_until_stable(timeout=0.2, poll_interval=0.1)
assert result is False
def test_wait_until_stable_invalid_timeout(self, mock_transport):
"""Test wait with negative timeout."""
driver = ThermalChamberDriver(mock_transport)
with pytest.raises(ValueError, match="Timeout must be non-negative"):
driver.wait_until_stable(timeout=-1.0)
def test_wait_until_stable_invalid_interval(self, mock_transport):
"""Test wait with non-positive poll interval."""
driver = ThermalChamberDriver(mock_transport)
with pytest.raises(ValueError, match="Poll interval must be positive"):
driver.wait_until_stable(poll_interval=0.0)
def test_set_ramp_rate(self, mock_transport):
"""Test ramp rate command."""
driver = ThermalChamberDriver(mock_transport)
driver.set_ramp_rate(5.0)
mock_transport.write.assert_called_once_with("TEMP:RAMP 5.00")
def test_get_ramp_rate(self, mock_transport):
"""Test ramp rate query."""
mock_transport.query.return_value = "5.00"
driver = ThermalChamberDriver(mock_transport)
rate = driver.get_ramp_rate()
assert rate == 5.0
class TestPowerSupplyDriver:
"""Tests for PowerSupplyDriver."""
def test_set_voltage(self, mock_transport):
"""Test voltage setpoint command."""
driver = PowerSupplyDriver(mock_transport)
driver.set_voltage(1, 3.3)
mock_transport.write.assert_called_once_with("VOLT 3.300")
def test_get_voltage(self, mock_transport):
"""Test voltage setpoint query."""
mock_transport.query.return_value = "3.300"
driver = PowerSupplyDriver(mock_transport)
voltage = driver.get_voltage(1)
assert voltage == 3.3
def test_set_current_limit(self, mock_transport):
"""Test current limit command."""
driver = PowerSupplyDriver(mock_transport)
driver.set_current_limit(1, 0.5)
mock_transport.write.assert_called_once_with("CURR 0.500")
def test_get_current_limit(self, mock_transport):
"""Test current limit query."""
mock_transport.query.return_value = "0.500"
driver = PowerSupplyDriver(mock_transport)
current = driver.get_current_limit(1)
assert current == 0.5
def test_measure_voltage(self, mock_transport):
"""Test voltage measurement."""
mock_transport.query.return_value = "3.305"
driver = PowerSupplyDriver(mock_transport)
voltage = driver.measure_voltage(1)
assert voltage == 3.305
mock_transport.query.assert_called_once_with("MEAS:VOLT?", None)
def test_measure_current(self, mock_transport):
"""Test current measurement."""
mock_transport.query.return_value = "0.125"
driver = PowerSupplyDriver(mock_transport)
current = driver.measure_current(1)
assert current == 0.125
mock_transport.query.assert_called_once_with("MEAS:CURR?", None)
def test_enable_output_on(self, mock_transport):
"""Test enable output command."""
driver = PowerSupplyDriver(mock_transport)
driver.enable_output(1, True)
mock_transport.write.assert_called_once_with("OUTP ON")
def test_enable_output_off(self, mock_transport):
"""Test disable output command."""
driver = PowerSupplyDriver(mock_transport)
driver.enable_output(1, False)
mock_transport.write.assert_called_once_with("OUTP OFF")
def test_is_output_enabled_true(self, mock_transport):
"""Test output enabled query - enabled."""
mock_transport.query.return_value = "1"
driver = PowerSupplyDriver(mock_transport)
assert driver.is_output_enabled(1) is True
def test_is_output_enabled_false(self, mock_transport):
"""Test output enabled query - disabled."""
mock_transport.query.return_value = "0"
driver = PowerSupplyDriver(mock_transport)
assert driver.is_output_enabled(1) is False
class TestMultimeterDriver:
"""Tests for MultimeterDriver."""
def test_measure_dc_voltage(self, mock_transport):
"""Test DC voltage measurement."""
mock_transport.query.return_value = "3.300000"
driver = MultimeterDriver(mock_transport)
voltage = driver.measure_dc_voltage()
assert voltage == 3.3
mock_transport.query.assert_called_once_with("MEAS:VOLT:DC?", None)
def test_measure_dc_current(self, mock_transport):
"""Test DC current measurement."""
mock_transport.query.return_value = "0.125000"
driver = MultimeterDriver(mock_transport)
current = driver.measure_dc_current()
assert current == 0.125
mock_transport.query.assert_called_once_with("MEAS:CURR:DC?", None)
def test_measure_resistance_not_implemented(self, mock_transport):
"""Test resistance measurement raises NotImplementedError."""
driver = MultimeterDriver(mock_transport)
with pytest.raises(NotImplementedError, match="Resistance measurement"):
driver.measure_resistance()
def test_set_integration_time_not_implemented(self, mock_transport):
"""Test integration time setting raises NotImplementedError."""
driver = MultimeterDriver(mock_transport)
with pytest.raises(NotImplementedError, match="Integration time"):
driver.set_integration_time(1.0)
def test_configure_dc_voltage(self, mock_transport):
"""Test configure for DC voltage."""
driver = MultimeterDriver(mock_transport)
driver.configure_dc_voltage()
mock_transport.write.assert_called_once_with("CONF:VOLT:DC")
def test_configure_dc_current(self, mock_transport):
"""Test configure for DC current."""
driver = MultimeterDriver(mock_transport)
driver.configure_dc_current()
mock_transport.write.assert_called_once_with("CONF:CURR:DC")
def test_get_configuration(self, mock_transport):
"""Test get current configuration."""
mock_transport.query.return_value = '"VOLT:DC"'
driver = MultimeterDriver(mock_transport)
config = driver.get_configuration()
assert config == "VOLT:DC"
mock_transport.query.assert_called_once_with("CONF?", None)
def test_read(self, mock_transport):
"""Test read measurement with current configuration."""
mock_transport.query.return_value = "3.300000"
driver = MultimeterDriver(mock_transport)
value = driver.read()
assert value == 3.3
mock_transport.query.assert_called_once_with("READ?", None)