I just started unit testing in python using pytest. Well, when I have a function with a return value, with the "assert" I can compare a certain value with the value that the function return. But if I had a void function that returns nothing and does a print at the end, for example:
def function() -> None:
number = randint(0, 4)
if (number == 0):
print("Number 0")
elif (number == 1):
print("Number 1")
elif (number == 2):
print("Number 2")
elif (number == 3):
print("Number 3")
elif (number == 4):
print("Number 4")
How can i test this simple function to get 100% code coverage?
One method I've found to test this function is to do a return of the value (instead of print) and print it later, and then use the assert. But I wanted to know if it was possible to avoid this and do a test directly on the print statemant.
CodePudding user response:
You can redirect sys.stdout
(the stream that print
writes to) to a buffer and then examine or assert the contents of the buffer.
>>> import io
>>> import contextlib
>>>
>>> def f():print('X')
...
>>> buf = io.StringIO()
>>> with contextlib.redirect_stdout(buf):
... f()
...
>>> print(repr(buf.getvalue()))
'X\n'
>>>
>>> buf.close()
(Recall that print() appends the value of its end
argument to the line, which defaults to '\n'
).
CodePudding user response:
I suggest having a look at the plugin pytest-mock. It allows you to mock collaborating objects of your code under test.
Consider the following code under test:
# production.py
def say_hello() -> None:
print('Hello World.')
you can easily mock this now with
# production_test.py
from production import say_hello
def test_greeting(mocker):
# The "mocker" fixture is auto-magicall inserted by pytest,
# once the extenson 'pytest-mock' is installed
printer = mocker.patch('builtins.print')
say_hello()
assert printer.call_count == 1
You can also assert the arguments the printer function was called with, etc. You will find a lot of details in their useful documentation.
Now, consider you do not want to access the printer
, but have a code with some undesirable side-effects (e.g. an operation takes forever, or the result is non-predictable (random).) Let's have another example, say
# deep_though.py
class DeepThought:
#: Seven and a half million years in seconds
SEVEN_HALF_MIO_YEARS = 2.366771e14
@staticmethod
def compute_answer() -> int:
time.sleep(DeepThought.SEVEN_HALF_MIO_YEARS)
return 42
yeah, I personally don't want my test suite to run 7.5 mio years. So, what do we do?
# deep_thought_test.py
from deep_thought import DeepThought
def test_define_return_value(mocker) -> None:
# We use the internal python lookup path to the method
# as an identifier (from the location it is called)
mocker.patch('deep_thought.DeepThought.compute_answer', return_value=12)
assert DeepThought.compute_answer() == 12
Two more minor remarks, not directly related to the post:
- A high code coverage (80% - 90%) is a good goal. I personally try to stck around 90-95%. However, 100% coverage is usually not necessary. Simple(!) data items and log-statements can usually be ignored.
- It's a good practice to use a logger, instead of
print
. See Question 6918493 for example.