Best practices for unit testing when breaking down functions into smaller ones

Say we have a function of the form

``````def func(num: int) -> int:
num = num + 1
num = 2 * num
num = num**3
return num
``````

and let us act like each line is a long computation so that we want to break down `func` into several functions. For example,

``````def _func_a(num: int) -> int:
return num + 1

def _func_b(num: int) -> int:
return 2 * num

def _func_c(num: int) -> int:
return num**3

def func(num: int) -> int:
num = _func_a(num)
num = _func_b(num)
num = _func_c(num)
return num
``````

which is a common pattern when writing and testing code. However, I find that it is often difficult to find the right balance in terms of unit testing.

Some people will only write unit tests for `func`. However, in my experience these are people that will write very long functions. Moreover, if we actual deal with complicated computations these unit tests will be horrible to maintain.

The next step would be to write some unit tests for each `_func_{a,b,c}`. Then, we could check that `func` calls `_func_{a,b,c}` with something like

``````    def test_calls_func_b(mocker):
with mocker.patch.object(main, "_func_b"):
_ = my_module.func(1)
my_module._func_b.assert_called_once()
``````

However, one might then debate that there is still considerable room for error. For example, a mistake like

``````def func(num: int) -> int:
num = _func_a(num)
_func_b(num).  # mistake: no assignment!
num = _func_c(num)
return num
``````

will go unnoticed. Thus, I often find myself in the situation of writing additional tests like

``````    def test_calls_func_b(mocker):
func_a_return_value = mocker.Mock()
with mocker.patch.object(main, "_func_b"), mocker.patch.object(
main, "_func_a", return_value=func_a_return_value):
_ = main.func(1)
main._func_b.assert_called_once_with(func_a_return_value)
``````

that check that what comes out of `_func_a` goes into `_func_b` and so so on (eventually what is return form `func` is the return value of `_func_c`). This is what I feel like provides good safety and will catch a good amount of mistakes. The only downside is that this quickly translates in having to have quite a few mocks in a single test - in particular, if there are more functions than just the three `_func_{a,b,c}` and these have several arguments each (here they only have on). Therefore, I met several people that absolutely disdain having these sort of tests and consider them overkill. Perhaps the refusal is also rooted that many people still find mocks complicated.

So my questions are:

• Is there anything that I could be improved, or written more efficiently?
• Are there any best practices for this? Any discussions in books, blog posts, guidelines?

Don't try make such decisions up-front - it is way easier to decide when growing your unit tests alongside with the development of your functions.

You wrote

let us act like each line is a long computation so that we want to break down `func` into several functions

but usually that is not what you start with. `func` starts as a small-to-medium sized function, with some unit tests. When it grows over time, you add more tests for it, and when it comes to the point where you want to refactor parts of it into smaller functions, you already have unit tests for `func` in place at that time.

If not, it is time to add a few before you this refactoring, to make sure it will not break anything. At this point in time, you can have only unit tests for `func`, since _func_a, _func_b, _func_c don't even exist yet!

After the refactoring, _func_a/b/c will initially still be "private" functions, implementation details of `func, which means unit testing them is still not a good idea - yet.

So the obvious followup question is, when does start to make sense writing unit tests for `_func_a` alone? And the answer now should be almost obvious as well:

• as soon as `_func_a` becomes a public function, something one can use on its own, or

• as soon as you want to evolve and test `_func_a` on its own, because testing it only through `func` makes it hard to establish a test you trust, maybe because it becomes hard to get all the input combinations, edge cases and failure modes right.

Sometimes, the development of `func` does not follow this top down approach. You may decide to work more in a bottom-up process, where you first develop `_func_a` as a building brick before introducing it into `func` (maybe even before `func` exists). Then it should be clear that you need unit tests for `_func_a` first. Still it is a good idea to write also unit tests for `func` (maybe it is more an integration test, but how you call it does not really matter).

The other followup question here is, at which point in time does it start to make sense to mock out _func_a/b/c and test `func` in isolation? My short answer here is "almost never" - at least when we only talk about pure functions. I would actually start to mock out a function like `_func_a` when it starts hindering me to test `func` by a good unit test on its own. For example, when `_func_a` makes database or network calls, or when `_func_a` makes a really complex, CPU intensive calculation, then it is time to mock it out.

In the end, this is a judgement call. Drawing the line between "implementation details" and stable building bricks in your code requires some experience. It definitely cannot be judged sensibly by looking at contrived function names like `func` or `_func_a/b_c`, one has to evaluate the real situation and make an informed decision.

I'm sure someone will provide you with a more detailed answer, but here are some things to consider:

Unit tests are used to verify that the behavior of a function is correct and to prevent regressions so that you can make changes to implementation details without affecting that behavior and therefore not requiring a change to the tests.

The obvious way to write them would be to test the public interface of the system under test, in this case the functions as a black box. This means you would test for various kinds of inputs that the outputs are correct. Do this for all inputs, and you are done :)

In your specific example, you could write these tests for `func_{a,b,c}` and also `func`. However, I would consider your proposal to test that arguments are passed in the correct order an implementation detail. Just test `func` in the same way: Given an input, verify the output (especially for pure functions).

Sometimes this black-box approach is not enough. In your case, you have dependencies (hard coded, in the form of `func_{a,b,c}` within your main function and want to make sure that they are called (more relevant if you're dealing with state, e.g. persisting stuff to a database, other side-effects). This is where it becomes tricky: What is an implementation detail that should not be checked, and what should be checked? I dont believe there is a general answer, but would consider:

• How likely is the implementation going to change?
• How much effort is a change in the implementation causing for changing the unit tests and mocks?
• Could this scenario better be handled by integration/end-to-end tests?