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.