Pytest-Installation and Getting Started

Pythons: Python 3.6, 3.7, 3.8, 3.9, PyPy3

Platforms: Linux and Windows

PyPI package name: pytest

Documentation as PDF: download
latest

pytest is a framework that makes building simple and scalable tests
easy. Tests are expressive and readable—no boilerplate code required.
Get started in minutes with a small unit test or complex functional test
for your application or library.

Install pytest{#getstarted} {#installation}

  1. Run the following command in your command line:
pip install -U pytest
  1. Check that you installed the correct version:
$ pytest --version
pytest 6.1.2

Create your first test {#simpletest}

Create a simple test function with just four lines of code:

# content of test_sample.py
def func(x):
    return x + 1


def test_answer():

That’s it. You can now execute the test function:

$ pytest
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 1 item

test_sample.py F                                                     [100%]

_______________________________ test_answer ________________________________

    def test_answer():
E        +  where 4 = func(3)

test_sample.py:6: AssertionError

The [100%] refers to the overall progress of running all test cases.
After it finishes, pytest then shows a failure report because func(3)
does not return 5.

::: {.note}
::: {.title}
Note
:::

You can use the assert statement to verify test expectations. pytest’s
Advanced assertion
introspection

will intelligently report intermediate values of the assert expression
so you can avoid the many names of JUnit legacy
methods
.
:::

Run multiple tests

pytest will run all files of the form test*.py or *_test.py in
the current directory and its subdirectories. More generally, it follows
standard test discovery rules <test discovery>{.interpreted-text
role=“ref”}.

Assert that a certain exception is raised

Use the raises <assertraises>{.interpreted-text role=“ref”} helper to
assert that some code raises an exception:

# content of test_sysexit.py
import pytest


def f():
    raise SystemExit(1)


def test_mytest():
    with pytest.raises(SystemExit):
        f()

Execute the test function with “quiet” reporting mode:

$ pytest -q test_sysexit.py
.                                                                    [100%]
1 passed in 0.12s

::: {.note}
::: {.title}
Note
:::

The -q/--quiet flag keeps the output brief in this and following
examples.
:::

Group multiple tests in a class

Once you develop multiple tests, you may want to group them into a
class. pytest makes it easy to create a class containing more than one
test:

# content of test_class.py
class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")

pytest discovers all tests following its
Conventions for Python test discovery <test discovery>{.interpreted-text
role=“ref”}, so it finds both test_ prefixed functions. There is no
need to subclass anything, but make sure to prefix your class with
Test otherwise the class will be skipped. We can simply run the module
by passing its filename:

$ pytest -q test_class.py
.F                                                                   [100%]
____________________________ TestClass.test_two ____________________________

self = <test_class.TestClass object at 0xdeadbeef>

    def test_two(self):
        x = "hello"
>       assert hasattr(x, "check")
E       AssertionError: assert False
E        +  where False = hasattr('hello', 'check')

test_class.py:8: AssertionError
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.12s

The first test passed and the second failed. You can easily see the
intermediate values in the assertion to help you understand the reason
for the failure.

Grouping tests in classes can be beneficial for the following reasons:

  • Test organization
  • Sharing fixtures for tests only in that particular class
  • Applying marks at the class level and having them implicitly apply
    to all tests

Something to be aware of when grouping tests inside classes is that each
test has a unique instance of the class. Having each test share the same
class instance would be very detrimental to test isolation and would
promote poor test practices. This is outlined below:

# content of test_class_demo.py
class TestClassDemoInstance:
    def test_one(self):
        assert 0

    def test_two(self):
        assert 0
$ pytest -k TestClassDemoInstance -q
FF                                                                   [100%]
______________________ TestClassDemoInstance.test_one ______________________

self = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef>

    def test_one(self):
>       assert 0
E       assert 0

test_class_demo.py:3: AssertionError
______________________ TestClassDemoInstance.test_two ______________________

self = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef>

    def test_two(self):
>       assert 0
E       assert 0

test_class_demo.py:6: AssertionError
FAILED test_class_demo.py::TestClassDemoInstance::test_one - assert 0
FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0
2 failed in 0.12s

Request a unique temporary directory for functional tests

pytest provides Builtin fixtures/function
arguments
to request
arbitrary resources, like a unique temporary directory:

# content of test_tmpdir.py
def test_needsfiles(tmpdir):
    print(tmpdir)
    assert 0

List the name tmpdir in the test function signature and pytest will
lookup and call a fixture factory to create the resource before
performing the test function call. Before the test runs, pytest
creates a unique-per-test-invocation temporary directory:

$ pytest -q test_tmpdir.py
F                                                                    [100%]
_____________________________ test_needsfiles ______________________________

tmpdir = local('PYTEST_TMPDIR/test_needsfiles0')

    def test_needsfiles(tmpdir):
        print(tmpdir)
>       assert 0
E       assert 0

test_tmpdir.py:3: AssertionError
--------------------------- Captured stdout call ---------------------------
PYTEST_TMPDIR/test_needsfiles0
FAILED test_tmpdir.py::test_needsfiles - assert 0
1 failed in 0.12s

More info on tmpdir handling is available at
Temporary directories and files <tmpdir handling>{.interpreted-text
role=“ref”}.

Find out what kind of builtin
pytest fixtures <fixtures>{.interpreted-text role=“ref”} exist with
the command:

pytest --fixtures   # shows builtin and custom fixtures

Note that this command omits fixtures with leading _ unless the -v
option is added.

Continue reading

Check out additional pytest resources to help you customize tests for
your unique workflow:

  • "cmdline{.interpreted-text role=“ref”}" for command line
    invocation examples
  • "existingtestsuite{.interpreted-text role=“ref”}" for working
    with pre-existing tests
  • "mark{.interpreted-text role=“ref”}" for information on the
    pytest.mark mechanism
  • "fixtures{.interpreted-text role=“ref”}" for providing a
    functional baseline to your tests
  • "plugins{.interpreted-text role=“ref”}" for managing and writing
    plugins
  • "goodpractices{.interpreted-text role=“ref”}" for virtualenv and
    test layouts