Testing
For this guide we will build upon the example AwesomeInstrumentProtocol
that we declared in the Walkthrough to explore the topic of testing.
Protocol Unit Testing
Within the Omnibus package there are pre-configured fixtures designed to simplify the complexity of testing your protocol.
To access Omnibus fixtures via pytest, simply add the following to your conftest file:
pytest_plugins = ["unitelabs.bus.testing.fixtures"]
One such fixture is mk_stubbed_protocol
, which takes a Protocol
subclass, a dictionary mapping between request bytes and return byte values, and the kwargs required for protocol initialization and returns an instance of your Protocol that mocks out device interaction and instead responds to commands sent via execute
based on the provided dictionary mapping. In this way we can test that our protocol is handling all the different responses that the device can send.
Here we will show how to use the fixture mk_stubbed_protocol
to create an instance of the provided Protocol
and mocks out the device responses based on a provided mapping between requests and responses. We can wrap this fixture into our own fixture as follows:
1. Protocol Test Fixture
import typing
import pytest
from unitelabs.awesome_instrument.io.awesome_instrument_protocol import AwesomeInstrumentProtocol
pytest_plugins = ["unitelabs.bus.testing.fixtures"]
@pytest.fixture
def mk_my_protocol(mk_stubbed_protocol):
def _mk_my_protocol(data: dict[bytes, typing.Union[bytes, list[bytes]]], **kwargs) -> AwesomeInstrumentProtocol:
return mk_stubbed_protocol(AwesomeInstrumentProtocol, data, **kwargs)
return _mk_my_protocol
By creating a fixture that returns a function, we are now able to call that function in all of our tests with a custom data dict and test our Protocol
s behavior under different conditions and when different responses are sent from the device. (Alternatively we could also just use the mk_stubbed_protocol
fixture directly, but creating a fixture will reduce repetition in our code).
2. Mock Device Communication
import pytest
from unitelabs.awesome_instrument.io.awesome_instrument_protocol import AwesomeInstrumentProtocol
from unitelabs.awesome_instrument.io.errors import NotOkException
async def test_should_get_status(mk_my_protocol):
protocol: AwesomeInstrumentProtocol = mk_my_protocol({b"status": b"ok"})
await protocol.open()
response = await protocol.get_status()
print(response)
assert response == b"ok"
async def test_should_raise_if_not_ok(mk_my_protocol):
protocol: AwesomeInstrumentProtocol = mk_my_protocol({b"status": b"not ok"})
await protocol.open()
with pytest.raises(NotOkException, match=r"Everything is not ok\."):
print(await protocol.get_status())
Here we use our mk_my_protocol
fixture to test all possible return values from the device and ensure that the our protocol method is behaving as expected given the known responses from the device.