I am writing the embedded firmware for an effect pedal. The pedal's ui consists of a few knobs a few buttons and a few leds and it consists of various control modes each corresponding to a seperate effect. So each mode changes the meaning of the knobs, the buttons and led indications.
This is implemented as a sum type of "presenters". Each presenter is injected with the corresponding effect model and the buttons + leds drivers. Each presenter has it's own set of shortcuts and button controls. Each presenter owns the corresponding view and event emitter.
I am trying to figure out how to write integration tests for these presenters. What I currently do is pinpoint the high - level button events required for each presenter. Then define a state machine using the high-level events and define the appropriate model/view interactions for each transition. Then I use model-based testing to test that the implemented state machine properly calls everything.
I independently test the high-level event emitter which produces the events for the state machine.
I find this helps a lot with properly testing the interactions since writing the model-based tests for the state machine using high-level events is much more expressive than using the buttons driver directly.
So as a summary each presenter basically just delegates to the event emitter and passes the events from the event emitter to the state machine (called controller in the code) which controls the model and view.
A final presenter may look like this:
void delay_presenter::update()
{
button_event_emitter_.poll();
bool exit_event = button_event_emitter_.test_and_clear_exit_event();
bool tap_event = button_event_emitter_.test_and_clear_tap_button_event();
std::optional<button_event> enable_event = button_event_emitter_.test_and_clear_enable_button_event();
bool alternative_modified = alternative_modifed_event_emitter_.poll();
if (exit_event)
controller_.handle_exit_event();
if (tap_event)
controller_.handle_tap_button_event();
if (enable_event)
controller_.handle_enable_button_event(*enable_event);
if (alternative_modified)
controller_.handle_alternative_modified_event();
controller_.handle_switch_position(gpios_.switch_position());
model_.set_inputs(inputs_);
view_.update();
}
I am in conflict in how to properly test these presenters without duplicating the test suit of the underlying controller objects but in a more verbose way. It seems that if I don't want to couple the presenter's tests to the underlying implementation, I will have to basically merge the event emitters' and controller's test suits together which makes the model-based testing much more verbose and complex. In fact it's often the case that I would have to use the event emitter object itself in the test suit too.
I think this leads to a more general problem on how one should test a component that basically just wires together other subcomponents that are heavily tested themselves and were built in a bottom-up approach.
One solution is to just make sure that the component wires the subcomponents together properly. In that case I would have to mock the controller and event emitters and make sure calls are delegate as appropriate. This would be the Mockist-approach to this problem. I can't think of any way of doing it without basically duplicating the actual code and making a very fragile test that will break with any future refactoring of the presenter. Maybe this is a good thing in this case since any change to the emitter + controller architecture would probably need a redesign of the test themselves anyway.
Another is to perhaps just write a few sanity check tests with a limited number of user scenarios. This won't be a complete test as it is with the model-based approach used on the controller state-machine object. I can't see this helping much in the case of a larger refactoring.
Going further how should one implement the final end-to-end test that actually checks all presenters? It seems one would have to either just check a very limited subset of use cases or do intrusive mock-based tests checking presenter destruction and creation.
Any thoughts?