Getting to Know Pytest: A Simple and Effective Testing Tool

--

FizzBuzz test using Pytest with test parametrization

We’re gonna use the “FizzBuzz” game to learn Pytest. Let’s start with how we write tests using the built-in module called unittest:

To check if the code above works, run:

$ python3 test_fizzbuzz.py
….
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
Ran 4 tests in 0.000s
OK

Using unittest is good since we don’t need to install any extra module. However, Pytest has nice features we can’t overlook. Let’s try it.

To install it, run the command below:

$ pipenv install pytest

If you’re not familiar with Pipenv yet, we do recommend you to go to Pipenv: Python Dev Workflow for Humans first. It’s a simple packaging tool for Python.

After install Pytest, we should be able to run the command below without any change in the code and get the same results.

$ pipenv run pytest
================== test session starts ===================
platform darwin — Python 3.7.0, pytest-4.0.1, py-1.7.0, pluggy-0.8.0
rootdir: /Users/zkan/pytest-checkup, inifile:
plugins: cov-2.6.0
collected 4 items
test_fizzbuzz.py …. [100%]================ 4 passed in 0.01 seconds ================

Pytest will find the tests in the files having prefix “test_” by default. As you can see, Pytest can work with unittest without any issue. 😎

Below is the entire code we rewrite in the Pytest style.

See the results below:

$ pipenv run pytest
====================== test session starts ======================
platform darwin — Python 3.7.0, pytest-4.0.1, py-1.7.0, pluggy-0.8.0
rootdir: /Users/zkan/pytest-checkup, inifile:
plugins: cov-2.6.0
collected 4 items
test_fizzbuzz.py …. [100%]
=================== 4 passed in 0.01 seconds ====================

Pytest style looks really nice and clean. We don’t need to define a TestCase class and put all test methods inside. For any assertion, we only need assert to check. Compare 2 code snippets below to get the idea.

Using unittest, we do:

self.assertContains('test', name)

Using Pytest, we do:

assert 'test' in name

Pretty neat, isn’t it? 😉

Wait! There are code duplicates, aren’t there? There 4 lines that say f = FizzBuzz(). No worries. We can remove this duplicate. We’ll use fixtures to fix it. Here is how:

What we did above are:

  1. Define a function that returns an object we’ll use in each test and put a decorator @pytest.fixture on the function;
  2. Take the function name as a parameter for each test.

Done! We should get the same results. 🎉

Let’s take a look at a feature that helps you deal with repetitive tests better! It’s test parametrization. We can combine all test cases into one and use this feature. Here is how:

Cool! 😎

One of the useful features we may use a lot is custom markers. This allows us to select specific tests for a run. For example, if we have 2 tests:

def test_send_email():
...

def test_render():
...

We want to run only test_send_email, we will mark that test with a custom metadata testemail like this:

@pytest.mark.testemail
def test_send_email():
...

def test_render():
...

When we run tests, we run:

$ pipenv run pytest -m testemail

It will run only the test test_send_email. 👍

When our test file is too long, we can move out the fixture into a new file called conftest.py and we don’t need to import this file back. Pytest will automatically import this file for us. See how we do below:

Note: We can move our FizzBuzz class into a different file for better code organization. To make it simple for now, we keep it in conftest.py.

The results should stay the same when we run Pytest.

Last but not least, we can run the code coverage check with Pytest too. We need to install pytest-cov:

$ pipenv install pytest-cov

We then run and see the results:

$ pipenv run pytest — cov=.
====================== test session starts =======================
platform darwin — Python 3.7.0, pytest-4.0.1, py-1.7.0, pluggy-0.8.0
rootdir: /Users/zkan/pytest-checkup, inifile:
plugins: cov-2.6.0
collected 8 items
test_fizzbuzz.py …….. [100%]
— — —coverage: platform darwin, python 3.7.0-final-0 — — —
Name Stmts Miss Cover
— — — — — — — — — — — — — — — — — — —
conftest.py 13 0 100%
test_fizzbuzz.py 3 0 100%
— — — — — — — — — — — — — — — — — — —
TOTAL 16 0 100%
==================== 8 passed in 0.05 seconds ====================

If we want to see the code coverage report in HTML format, run:

$ pipenv run pytest — cov=. — cov-report html

That’s it for today! Enjoy testing! 🎉

--

--

Kan Ouivirach
PyCon Thailand — Host of PyConAPAC 2021

Data Craftsman. Passionate in software engineering, data engineering, and data science. ♥