Skip to content

Distinguish shutter types in existing tests#1068

Merged
DominicOram merged 8 commits intomainfrom
1066_unit_test_exp_shutter
Mar 25, 2025
Merged

Distinguish shutter types in existing tests#1068
DominicOram merged 8 commits intomainfrom
1066_unit_test_exp_shutter

Conversation

@CoePaul
Copy link
Contributor

@CoePaul CoePaul commented Feb 21, 2025

  • Renames ambiguously named "shutter tests" according to their specific shutter type Examples (of renaming) covered here: shutter -> zebra_shutter shutter -> hutch_shutter

Fixes ambiguous names ( side work for Issue #1066 )

Instructions to reviewer on how to test:

  1. Run all tests in project
  2. Confirm tests all pass

Checks for reviewer

  • Would the PR title make sense to a scientist on a set of release notes
  • If a new device has been added does it follow the standards
  • If changing the API for a pre-existing device, ensure that any beamlines using this device have updated their Bluesky plans accordingly
  • Have the connection tests for the relevant beamline(s) been run via dodal connect ${BEAMLINE}

@CoePaul CoePaul requested a review from a team as a code owner February 21, 2025 16:56
@codecov
Copy link

codecov bot commented Feb 21, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.75%. Comparing base (f135ed4) to head (8f92c86).
Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1068   +/-   ##
=======================================
  Coverage   97.75%   97.75%           
=======================================
  Files         170      170           
  Lines        6849     6851    +2     
=======================================
+ Hits         6695     6697    +2     
  Misses        154      154           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@DominicOram DominicOram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks! Mostly minor points. As discussed, I think these should also be in test_hutch_conditional_shutter.py.

Comment on lines +15 to +19
fake_prefix = "I74"
shutter_name = "GrandOldShutter"
hcs = HutchConditionalShutter(fake_prefix, hutch_controlling_shutter, shutter_name)
test_case = TestCase()
test_case.assertIsInstance(hcs, HutchConditionalShutter)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should: We use pytest rather than unittest. pytest sort of uses the assert keyword instead so the way to do this in pytest is:

Suggested change
fake_prefix = "I74"
shutter_name = "GrandOldShutter"
hcs = HutchConditionalShutter(fake_prefix, hutch_controlling_shutter, shutter_name)
test_case = TestCase()
test_case.assertIsInstance(hcs, HutchConditionalShutter)
fake_prefix = "I74"
shutter_name = "GrandOldShutter"
hcs = HutchConditionalShutter(fake_prefix, hutch_controlling_shutter, shutter_name)
assert isinstance(hcs, HutchConditionalShutter)

Comment on lines +22 to +33
async def test_shutter_created_for_eh_one_without_raising_errors():
hutch_from_which_shutter_is_controlled = HutchState.EH1
await unit_test_shutter_created_without_raising_errors(
hutch_from_which_shutter_is_controlled
)


async def test_shutter_created_for_eh_two_without_raising_errors():
hutch_from_which_shutter_is_controlled = HutchState.EH2
await unit_test_shutter_created_without_raising_errors(
hutch_from_which_shutter_is_controlled
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could: You can use @pytest.mark.parametrize to do this for you:

Suggested change
async def test_shutter_created_for_eh_one_without_raising_errors():
hutch_from_which_shutter_is_controlled = HutchState.EH1
await unit_test_shutter_created_without_raising_errors(
hutch_from_which_shutter_is_controlled
)
async def test_shutter_created_for_eh_two_without_raising_errors():
hutch_from_which_shutter_is_controlled = HutchState.EH2
await unit_test_shutter_created_without_raising_errors(
hutch_from_which_shutter_is_controlled
)
@pytest.mark.parametrize(
"hutch_from_which_shutter_is_controlled", [HutchState.EH1, HutchState.EH2]
)
async def test_shutter_created_without_raising_errors(
hutch_from_which_shutter_is_controlled,
):
await unit_test_shutter_created_without_raising_errors(
hutch_from_which_shutter_is_controlled
)

This will create two tests called test_shutter_created_without_raising_errors[EH1] and test_shutter_created_without_raising_errors[EH2].

In the case of just having the two options it's neither here nor there but when there's like 3 or 4 it tidies up a lot of the copy/paste.

)


async def unit_test_shutter_created_without_raising_errors(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could: This actually need not be an async function. To a first approximation an async function is only needed if you are calling other async functions from it. The constructor of HutchConditionalShutter is synchronous and so this isn't needed. If you were calling the set here it would be needed. Does no harm though so not the end of the world to leave it in.

Comment on lines +38 to +39
with pytest.raises(HutchInvalidError):
await unit_test_shutter_created_without_raising_errors(not_a_hutch)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It's a little odd that you're calling a function called unit_test_shutter_created_without_raising_errors and expecting to actually raise the error. But not the end of the world.

@CoePaul CoePaul requested a review from DominicOram March 6, 2025 12:44
Copy link
Contributor

@DominicOram DominicOram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thank you! Snake_case is the only comment I really care about, happy for you to take/leave the others.

Comment on lines +19 to +44
@pytest.fixture
async def eh1_shutter(RE: RunEngine) -> HutchConditionalShutter:
shutter = HutchConditionalShutter("", HutchState.EH1, name="mock_shutter")
await shutter.connect(mock=True)

def set_status(value: ShutterDemand, *args, **kwargs):
value_sta = ShutterState.OPEN if value == "Open" else ShutterState.CLOSED
set_mock_value(shutter.shutter.status, value_sta)

callback_on_mock_put(shutter.shutter.control, set_status)

return shutter


@pytest.fixture
async def eh2_shutter(RE: RunEngine) -> HutchConditionalShutter:
shutter = HutchConditionalShutter("", HutchState.EH2, name="mock_shutter")
await shutter.connect(mock=True)

def set_status(value: ShutterDemand, *args, **kwargs):
value_sta = ShutterState.OPEN if value == "Open" else ShutterState.CLOSED
set_mock_value(shutter.shutter.status, value_sta)

callback_on_mock_put(shutter.shutter.control, set_status)

return shutter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could: I would combine the common bits here:

async def make_mock_shutter(hutch: HutchState) -> HutchConditionalShutter:
    shutter = HutchConditionalShutter("", hutch, name="mock_shutter")
    await shutter.connect(mock=True)

    def set_status(value: ShutterDemand, *args, **kwargs):
        value_sta = ShutterState.OPEN if value == "Open" else ShutterState.CLOSED
        set_mock_value(shutter.shutter.status, value_sta)

    callback_on_mock_put(shutter.shutter.control, set_status)

    return shutter


@pytest.fixture
async def eh1_shutter(RE: RunEngine) -> HutchConditionalShutter:
    return await make_mock_shutter(HutchState.EH1)


@pytest.fixture
async def eh2_shutter(RE: RunEngine) -> HutchConditionalShutter:
    return await make_mock_shutter(HutchState.EH2)

Comment on lines +149 to +150
def assertIsInstance(an_instance, expected_type):
assert isinstance(an_instance, expected_type)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should: This should be snake case as a python function

Suggested change
def assertIsInstance(an_instance, expected_type):
assert isinstance(an_instance, expected_type)
def assert_is_instance(an_instance, expected_type):
assert isinstance(an_instance, expected_type)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could: I would also personally argue that assert_is_instance(x, y) is no more readable than assert isinstance(x, y) and so feel this function is redundant but I don't feel that strongly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might well be "no more readable" but it is more ophiomorphic ( Greek ophis = snake )

CoePaul added 6 commits March 7, 2025 14:25
* Renames ambiguously named "shutter tests" according
   to their specific shutter type
   Examples (of renaming) covered here:
    shutter -> zebra_shutter
    shutter -> hutch_shutter
* i19 specific tests cover a HutchConditionalShutter

* Therefore the test script has been renamed to account for this
  specificity in the shutter type
* First round of tests check instantiation against all enum types in
  HutchState ( EH1, EH2 and Invalid )

* Required code changes to HutchConditionalShutter class to make
  constructor raise error if instantiated with HutchState.INVALID
  as the requesting hutch from which the shutter is being controlled

** N.B. This might spark a philosophical debate on the nature of
   HutchState enum and whether it is the correct type to pass in
   here through the constructor
* Merges all tests into one hutch test file
  ( since "unit" tests were not unit level after all )
* Adds comments throughout test file
   - to label test types Constructor, Functional etc
   - to separate and delineate happy / unhappy path tests
   - to label auxiliary functions

* Removes unittest assertIsInstance
  - local version rolled in test file
  - this is to comply with not using unittest
    assertions ( avoidance being a local de facto standard )

* Replaces spuriously named functions

* Adds tests to complete symmetry of EH1 / EH2
* Assertion method (auxiliary to i19 hutch shutter tests)
  is now rendered in snake case
@CoePaul CoePaul force-pushed the 1066_unit_test_exp_shutter branch from f99287a to 37cb4bc Compare March 7, 2025 14:27
@DominicOram DominicOram self-requested a review March 24, 2025 17:13
Copy link
Contributor

@DominicOram DominicOram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thank you!

@DominicOram DominicOram changed the title dodal#1066 distinguish shutter types in existing tests Distinguish shutter types in existing tests Mar 25, 2025
@DominicOram DominicOram merged commit 4b701bb into main Mar 25, 2025
19 checks passed
@DominicOram DominicOram deleted the 1066_unit_test_exp_shutter branch March 25, 2025 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants