Testing as Confidence Booster
“We’ll get the interns to write our tests later.” That quote was from a manager explaining his team’s testing strategy during a product udpate. No one else in the room or on the call blinked.
I was shocked. I had a feeling that my company wasn’t great at testing. It was obvious from the number of outages across our products at any given time that testing wasn’t a priority. To hear a manager, however, publicly say that the team’s current engineers, the ones who were writing code that was being deployed into production, would not be the ones writing tests–I mean, major WTF?!
While I love tests, I don’t always enjoy writing them. Test code is not sexy. In fact, it’s usually pretty repetitive. Write a bit of code to perform setup tasks; write a bit of code to perform the test; write a bit of code to tear down the test.
That said, I cannot imagine life without tests. Why?
Tests breed confidence.
A project doesn’t start out with perfect test coverage–“perfect” meaning 100% coverage. (In fact, 100% test coverage shouldn’t actually be the goal.) But, from the beginning of a project, it should start with some tests, covering perhaps 30-40-50 percent of the codebase. Overtime, as the project grows, new features are added, and bugs are uncovered (in production undoubtedly), you add more tests. Maybe, test coverage hits 100%, but if it gets to 80%, 90%, that may be good enough.
What the tests provide isn’t a badge for you to display to the world. “Look, my codebase has 97% test coverage!” Rather, tests provide you with confidence to make changes–sometimes radical changes. Lets look at an example to demonstrate.
We can use the example to calculate a factorial from here, which I found by doing a search for “iteration vs recursion”.
# recursion
def factorial(n):
if (n == 0):
return 1;
return n * factorial(n - 1);
To write a test for the above function is straightforward.
import pytest
class TestFactorial:
def test_factorial(self):
f = factorial(5)
assert f == 120
Then run the tests:
pytest test_factorial.py
===================================================== test session starts =====================================================
platform linux -- Python 3.8.2, pytest-6.1.1, py-1.9.0, pluggy-0.13.1
rootdir: /home/tjb/personal/personal-blog/examples
plugins: cov-2.10.1, timeout-1.4.2
collected 1 item
test_factorial.py . [100%]
Now, lets say a month goes by, and I learn that instead of using recursion, I can do the same
thing with iteration. I change the implementation of the factorial
function:
# iteration
def factorial(n):
res = 1;
for i in range(2, n + 1):
res *= i;
return res;
And rerun the tests:
pytest test_factorial.py
===================================================== test session starts =====================================================
platform linux -- Python 3.8.2, pytest-6.1.1, py-1.9.0, pluggy-0.13.1
rootdir: /home/tjb/personal/personal-blog/examples
plugins: cov-2.10.1, timeout-1.4.2
collected 1 item
test_factorial.py . [100%]
While this example may seem trivial, it still shows the power of testing. The test focuses on the input and output of the function being tested. Once I have the test and working code, I can change the implementation as I see fit, knowing that the test will tell me if I do something foolish. Now, the only way I can introduce a bug into production, is if I either don’t run the test before committing my change, or worse, run the test but ignore any failures.