In Python, consider a function like the following:
def main(*args):
value1 = pure_function1(*args)
if condition(value1):
value = side_effect1(value1)
if value:
value2 = pure_function2(value)
side_effect2(value2)
else:
value3 = pure_function3(value)
value4 = side_effect3(value3)
value5 = pure_function3(value4)
side_effect4(value5)
else:
side_effect5()
Assuming that unit tests have been written for condition
and all pure_functionX
, which strategies are advisable to test the rest of the logic?
Writing unit tests with mocks seems cumbersome and hard to maintain because various mocks would be needed, and they have to be set up to cover all the possible branches (e.g. side_effect1
should be set to return both, truthy and falsy values). This can become even more complicated if more branches or deeper nested levels are involved.
On the other hand, testing all the paths via integration tests also carries their share of problems. Setting up the services/dependencies to cover all paths is often more complicated than doing it with mocks. Moreover, if the side effects are slow, the costs of running the tests could be prohibitive.
I was also exploring restructuring or creating an abstraction to deal with the issue, but I couldn't find a way to mitigate it meaningfully. Extracting a function/class that would cover the inner conditional would require passing several dependencies as parameters, and I don't see how it would simplify testing.
I also cannot find a way to separate the remaining decision logic from the side effects any further than it already is. I thought about converting the function to a generator containing only the branching and the pure functions, which yields some data object whenever a side effect is needed. The function executing the generator would then perform the side effect according to the data object, and pass the resulting value back to the generator. This approach involves introducing a function to run the generator, and I haven't seen it often used in Python, which makes me believe it can be found clunky and non-Pythonic.
Right now I am favoring a solution that mixes unit tests with mocks, and integration tests. Before going there, however, I would like to know if there is some obvious or common strategy to deal with this scenario that I am simply missing. I suspect this situation is found rather often.
Edit: to provide a bit better of context on what the side_effectX
functions could be, they can be pictured as blocking network calls that perform an operation on a machine, e.g. open_valve(), start_engine(), increase_pressure(), etc.