When in the refactoring step of a TDD process, if we "factor out" some common functionality from two (or more) code modules, how do we avoid that "factored out" code not being properly covered by unit tests?
Here is a toy example to illustrate what I mean. Since it is a toy example, the factored-out code is very small, but we could imagine that it was a much larger set of code that was common between two modules. Let's say we have arrived at the following situation by following a TDD process:
# ----- foo.py -------
def foo(a: int, b:int) -> str:
x = str(a + b)
... # some elided operations that modify x further
return x
# ----- bar.py ------
def bar(a: int, b: int) -> str:
x = str(a + b)
... # some different operations that modify x further
return x
In the refactoring step, we want to break out the str(a + b)
part of the two functions:
After:
# ---- baz.py ----
def stringy_sum(a: int, b: int) -> str:
return str(a + b)
# ---- foo.py ----
def foo(a: int, b:int) -> str:
x = stringy_sum(a, b)
... # some elided operations that modify x further
return x
# ----- bar.py ------
def bar(a: int, b: int) -> str:
x = stringy_sum(a, b)
... # some different operations that modify x further
return x
Now we have a function stringy_sum
that is not covered by any unit tests of its own. It's only covered by what could be regarded as integration tests (the tests for foo and bar still use the function, but do not actually test its operation). Where did we go wrong in this process from a TDD standpoint?
is not covered by any unit tests of its own
does not mean that it is not covered by unit tests. Your original unit tests, with zero modification, should already be testing the logic of the factored out code. Otherwise you are not achieving good code coverage.foo(a, b)
equal tofoo_helper(stringy_sum(a, b))
, then write tests forfoo_helper
(as well asstringy_sum
) instead offoo
; similarly withbar
. I'm not saying I recommend it, but you can do it.