Skip to content

Commit 2c46463

Browse files
move patch_motors to dodal.testing (#1513)
* move patch_motors to dodal.testing * Added arguments for default values with doc string * Updated docs --------- Co-authored-by: Dominic Oram <[email protected]>
1 parent a004677 commit 2c46463

File tree

16 files changed

+85
-49
lines changed

16 files changed

+85
-49
lines changed

docs/how-to/write-tests.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ Testing is essential to maintain the integrity and reliability of the codebase.
77
- **Unit Tests**: Place unit tests for individual components in the `tests` directory, but take care to mirror the file structure of the `src` folder with the corresponding code files. Use the `test_*.py` naming convention for test files.
88
- **System Tests**: Tests that interact with DLS infrastructure, network, and filesystem should be placed in the top-level `systems_test` folder. This separation ensures that these tests are easily identifiable and can be run independently from unit tests.
99

10+
Useful functions for testing that can be reused across multiple tests for common devices and for external plan repositories belong in the `dodal/testing` directory. For example, when mocking a `Motor` device, all of the signals will default to zero, which will cause errors when trying to move. The `patch_motor` and `patch_all_motors` functions, found in `dodal.testing`, will populate the mocked motor with useful default values for the signals so that it can still be used in tests.
11+
12+
1013
## Writing a test for a device
1114
We aim for high test coverage in dodal with small, modular test functions. To achieve this, we need to test the relevant methods by writing tests for the class/method we are creating or changing, checking for the expected behaviour. We shouldn't need to write tests for parent classes unless we alter their behaviour.
1215

src/dodal/devices/util/test_utils.py

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/dodal/testing/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .setup import patch_all_motors, patch_motor
2+
3+
__all__ = ["patch_motor", "patch_all_motors"]

src/dodal/testing/setup.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from contextlib import ExitStack
2+
3+
from ophyd_async.core import Device
4+
from ophyd_async.epics.motor import Motor
5+
from ophyd_async.testing import (
6+
callback_on_mock_put,
7+
set_mock_value,
8+
)
9+
10+
11+
def patch_motor(
12+
motor: Motor,
13+
initial_position: float = 0,
14+
deadband: float = 0.001,
15+
velocity: float = 3,
16+
max_velocity: float = 5,
17+
low_limit_travel: float = float("-inf"),
18+
high_limit_travel: float = float("inf"),
19+
):
20+
"""
21+
Patch a mock motor with sensible default values so that it can still be used in
22+
tests and plans without running into errors as default values are zero.
23+
24+
Parameters:
25+
motor: The mock motor to set mock values with.
26+
initial_position: The default initial position of the motor to be set.
27+
deadband: The tolerance between readback value and demand setpoint which the
28+
motor is considered at position.
29+
velocity: Requested move speed when the mock motor moves.
30+
max_velocity: The maximum allowable velocity that can be set for the motor.
31+
low_limit_travel: The lower limit that the motor can move to.
32+
high_limit_travel: The higher limit that the motor can move to.
33+
"""
34+
set_mock_value(motor.user_setpoint, initial_position)
35+
set_mock_value(motor.user_readback, initial_position)
36+
set_mock_value(motor.deadband, deadband)
37+
set_mock_value(motor.motor_done_move, 1)
38+
set_mock_value(motor.velocity, velocity)
39+
set_mock_value(motor.max_velocity, max_velocity)
40+
set_mock_value(motor.low_limit_travel, low_limit_travel)
41+
set_mock_value(motor.high_limit_travel, high_limit_travel)
42+
return callback_on_mock_put(
43+
motor.user_setpoint,
44+
lambda pos, *args, **kwargs: set_mock_value(motor.user_readback, pos),
45+
)
46+
47+
48+
def patch_all_motors(parent_device: Device):
49+
"""
50+
Check all children of a device and patch any motors with mock values.
51+
52+
Parameters:
53+
parent_device: The device that hold motor(s) as children.
54+
"""
55+
motors = []
56+
57+
def recursively_find_motors(device: Device):
58+
for _, child_device in device.children():
59+
if isinstance(child_device, Motor):
60+
motors.append(child_device)
61+
recursively_find_motors(child_device)
62+
63+
recursively_find_motors(parent_device)
64+
motor_patch_stack = ExitStack()
65+
for motor in motors:
66+
motor_patch_stack.enter_context(patch_motor(motor))
67+
return motor_patch_stack

tests/devices/aithre_lasershaping/test_goniometer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from dodal.beamlines import aithre
77
from dodal.devices.aithre_lasershaping.goniometer import Goniometer
8-
from dodal.devices.util.test_utils import patch_all_motors
8+
from dodal.testing import patch_all_motors
99

1010

1111
@pytest.fixture

tests/devices/i03/test_undulator_dcm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
from dodal.devices.i03.dcm import DCM
1111
from dodal.devices.i03.undulator_dcm import UndulatorDCM
1212
from dodal.devices.undulator import AccessError, Undulator
13-
from dodal.devices.util.test_utils import patch_motor
1413
from dodal.log import LOGGER
14+
from dodal.testing import patch_motor
1515
from tests.devices.test_daq_configuration import MOCK_DAQ_CONFIG_PATH
1616
from tests.devices.test_daq_configuration.lookup import (
1717
BEAMLINE_ENERGY_DCM_PITCH_CONVERTER_TXT,

tests/devices/i24/test_pmac.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
EncReset,
1414
LaserSettings,
1515
)
16-
from dodal.devices.util.test_utils import patch_all_motors
16+
from dodal.testing import patch_all_motors
1717

1818

1919
@pytest.fixture

tests/devices/i24/test_vgonio.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from bluesky import RunEngine
44

55
from dodal.devices.i24.vgonio import VerticalGoniometer
6-
from dodal.devices.util.test_utils import patch_all_motors
6+
from dodal.testing import patch_all_motors
77

88

99
@pytest.fixture

tests/devices/mx_phase1/test_beamstop.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
1212
from dodal.devices.i03 import Beamstop, BeamstopPositions
13-
from dodal.devices.util.test_utils import patch_motor
13+
from dodal.testing import patch_motor
1414
from tests.common.beamlines.test_beamline_parameters import TEST_BEAMLINE_PARAMETERS_TXT
1515

1616

tests/devices/oav/test_oav_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
wait_for_tip_to_be_found,
1919
)
2020
from dodal.devices.smargon import Smargon
21-
from dodal.devices.util.test_utils import patch_all_motors
21+
from dodal.testing import patch_all_motors
2222

2323

2424
def test_bottom_right_from_top_left():

0 commit comments

Comments
 (0)