I'm trying to reduce the monolithicness of my code to make it into smaller/easier digestible units, but I'm not sure what the best practices are with testing. Here is an example:
def a(some_input):
...
def b(other_input):
# Bunch of logic, possible other function calls
if some_condition:
a(...)
Testing a
seems straightforward to me, but when it comes to b
, should I be testing that when b
is called with input that leads to it calling a
calls a
(in python that's something like a Mock.assert_called_with
, or should I just be testing that the output is correct?
My view on this is let's say re-use a
all over and break a
; I think that's a bunch of false positive test failures since the surrounding logic/behavior is correct, and simply a
should be fixed.
CodePudding user response:
Test documented outputs and side effects.
For example, if A is just making a sub-calculation which contributes to the output of B, unit tests of B should ignore it. A is an internal implementation detail and can change at any time, B might even stop calling A; if the implementation changes the test breaks. Only the documented return value of B should be tested. assert_eq(b(10), 5)
or whatever.
Let's say A is a thing which sends an email. That is a documented side effect of calling B which should be tested. Rather than set up a fake mail server and all that, you would mock the call to A and test that B passed in the correct values. A would be unit tested separately. If A is complicated enough you might split A out into a separate object which just sends email and have B use an instance of A.
In well-factored code, each subsystem is managed by their own object. Let's say B is totaling up the price of a sale, and A is doing complex tax calculations. A should be its own object which just does tax calculations, unit test A. When unit testing B, B doesn't really care what A returns, so long as the tax is included. The unit tests for B can mock A to return a fixed value and test the tax value from A is included in the result of B. With A and B thoroughly unit tested, you can do a simple integration test which calls B and the real A.