Jump to content

pytest

From Wikipedia, the free encyclopedia

This is an old revision of this page, as edited by Thomas Meng (talk | contribs) at 20:48, 23 May 2022 (moved popularity section back). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

Pytest
Original author(s)Krekel et al.
Stable release
7.1.1
Repository
Written inPython
PlatformmacOS, Windows, POSIX
TypeSoftware testing framework.
LicenseMIT License
Websitepytest.org Edit this on Wikidata

Pytest is a software testing framework based on the Python programming language. It can be used to write various types of software tests, including unit tests, integration tests, end-to-end tests, and functional tests. Its features include parametrized testing, fixtures, and assert re-writing.[1][2]

Pytest has been described as a scalable framework with less boilerplate code than other testing frameworks. Pytest fixtures provide the contexts for tests by passing in parameter names in test cases; its parametrization eliminates duplicate code for testing multiple sets of input and output; and its re-writed assert statements provides detailed output for causes of failures.[1]

Many technology projects have switched to pytest from other testing frameworks, including those of Mozilla and Dropbox. According to developer security platform Snyk, Pytest is classified as a key ecosystem project in Python due to its high degree of popularity.[3]

History

Pytest had began as part of PyPy, an alternative implementation of Python to CPython. It later separated from PyPy into its own project and was named pytest.[4]

Since early 2003 when PyPy emerged, it held a heavy emphasis on testing. PyPy had unit tests for newly written code, regressions tests for bugs, and integration tests using CPython's test suite.[5] In mid 2004, a testing framework called utest emerged, and contributors to PyPy began converting existing test cases to utest. Meanwhile, for EuroPython 2004, a complementary standard library for testing named std was created, which layed out the principles, such as assert rewriting, of what later became pytest.[4]

In late 2004, the std project was renamed to py and std.utest became py.test, afterwhich the py library was separated from PyPy. In November 2010, pytest 2.0.0 was released as a package separate from py, being still called py.test until August 2016 with the release of pytest 3.0.0 when the recommended command line entry point became pytest.[4]

Notable features

Parametrized testing

It is a common pattern in software testing to send values through test functions and check for correct output. In many cases, in order to thoroughly test functionalities, one needs to test multiple sets of input/outputs, and writing such cases separately would cause duplicate code as most of the actions would remain the same, only differing in input/output values. Pytest's parametrized testing feature eliminates such duplicate code by combining different iterations into one test case, then runs these iterations and displays each test's result separately.[1]

This feature is preferred by some developers over unittest's parametrizing method where multiple test results are grouped into one, in which case if one test fails and the rest passes, unittest only displays a single failing result for the entire group, which contrasts with pytest's individual test reporting.[2]

Parametrized tests in pytest are marked by the @pytest.mark.parametrize(argnames, argvalues) decorator, where the first parameter, argnames, is a string of comma-separated names, and argvalues is a list of values to pass into argnames. When there are multiple names in argnames, argvalues would be a list of tuples where values in each tuple corresponds to the names in argnames by index. The names in argnames are then passed into the test function makred by the decorator as parameters. When pytest runs such decorated tests, each pair of argnames and argvalues would constitute a separate run with its own test output and unique identifier. The identifier can then be used to run individual data pairs.[1]

Assert rewriting

When writing software tests, the assert statement is a primary means for communicating test failure, where expected values are compared to actual values.[1] While Python's builtin assert keyword would only raise AssertionError with no details in cases of failure, pytest rewrites Python's assert keyword and provides detailed output for the causes of failures, such as what expressions in the assert statement evaluate to. Its concision and readability drive many to use pytest over other testing frameworks. For instance, a comparison can be made with unittest's assert statements:

pytest unittest
assert x assertTrue(x)
assert x == y assertEqual(x, y)
assert x >= y assertLessEqual(x, y)

unittest adheres to a more verbose syntax because it is written with inspiration from JUnit in Java, as are most unit testing libraries. However, being considered by some Python developers as a more pythonic approach, pytest takes advantage of Python's ability for user defined libraries to achieve what in other languages would require compiler modifications to display traceback that provides information on the exact reasons of failed assertions, hence this assert rewriting feature.[6]

Pytest fixtures

Pytest fixtures provide the context for tests. They can be used to set a system in to known state and to pass dataset into test functions. Fixtures practically constitute the arrange phase in the anatomy of a test (AAA, short for arrange, act, assert).[7] Pytest fixtures can run before test cases as setup or after test cases for clean up, though they are different from unittest and nose's setup and teardowns. Functions declared as pytest fixtures are marked by the @pytest.fixture() decorator, whose names can then be passed into test functions as parameters.[8] When pytest finds the fixtures names in test functions parameters, it first searches in the same module for such fixtures, and if not found, it searches for such fixtures in the conftest.py file.[1]

For example:

import pytest

@pytest.fixture()
def dataset():
    """Return some data to test functions"""
    return {'data1': 1, 'data2': 2}

def test_dataset(dataset):
    """test and confirm fixture value"""
    assert dataset == {'data1': 1, 'data2': 2}

In the above example, pytest fixture dataset returns a dictionary, which is then passed into test function test_dataset for assertion. In addition to fixture detection within the same file as test cases, pytest fixtures can also be placed in the conftest.py file in the tests directory. There can be multiple conftest.py files, each placed within a tests directory for fixtures to be detected for each sebset of tests.[1][2]

Fixture scopes

In pytest, fixure scopes let the user define when a fixure should be called. There are four fixture scopes: function scope, class scope, module scope, and session scope. Function-scoped fixtures are default for all pytest fixtures, which are called every time a function having the fixture as a parameter runs. The goal of specifying a broader fixture scope is to eliminate repeated fixture calls, which could slow down test execution. Class-scoped fixtures are called once per test class, regardless of the number of times they are called, and the same logic goes for all other scopes. When changing fixture scope, one need only add the scope parameter to fixture decorators, for example, @pytest.fixture(scope="class").[1][9]

Test filtering

Another notable feature of pytest is its ability to filter through tests, where only desired tests are selected to run, or behave in a certain way as desired by the developer.[1]

With the "k" option (e.g. pytest -k some_name), pytest would only runs tests whose names include some_name. The opposite is true, where one can run pytest -k "not some_name", and pytest will run all tests whose names do not include some_name.[10]

Pytest's markers can, in addition to altering test behaviour, also filter tests. Pytest's markers are Python decorators starting with the @pytest.mark.<markername> syntax placed on top of test functions. With different arbitrarily named markers, running pytest -m <markername> on the command line will only run those tests decorated with such markers. All available markers can be listed by the pytest --markers along with their descriptions; custom markers can also be defined by users and registered in pytest.ini, in which case pytest --markers will also list those custom markers along with builtin markers.[11][12]

Popularity

Pytest was developed as part of an effort by third-party packages to address Python's built-in module unittest's shortcomings, and has been classified by developer security platform Snyk as one of the key ecosystem projects in Python due to its popularity. Some well-known cases of projects switching to pytest from unittest and nose (another testing package) include those of Mozilla and Dropbox.[13][14][15][3]

Installation and running tests

One needs to run pip install pytest from the command line. After entering pytest on the command line, pytest then recursively detects and runs tests that have either test_ or _test as function names in files within the working directory. Files containing tests are named according to the following format: test_*.py or *_test.py; Test classes are named such that they either begin or end with Test. This is the default naming convention pytest uses for detection, though it can be customized in pytest's configuration file pytest.ini.[2][1]

For instance, one can define a test in a file named test_pass.py as follows:

def test_pass():
    assert 1 == 1

Then, enter pytest test_pass.py on the command line within the project directory where the test function resides. The output is as follows:

===================== test session starts ======================
collected 1 items
test_one.py .
=================== 1 passed in 0.01 seconds ===================

Pytest uses a . beside the file under test to signify a passing test, and F for a failing test, along with various other test output behaviours which are all customizable.[1]

See also

References

  1. ^ a b c d e f g h i j k Okken, Brian (September 2017). Python Testing with Pytest (1st ed.). The Pragmatic Bookshelf. ISBN 9781680502404. Retrieved 22 January 2022.
  2. ^ a b c d Hillard, Dave. "Effective Python Testing With Pytest". Real Python. Retrieved 12 February 2022.
  3. ^ a b "pytest". Snyk. Retrieved 12 May 2022.
  4. ^ a b c "History". pytest. Retrieved 13 April 2022.
  5. ^ Bolz-Tereick, Carl Friedrich. "PyPy Status Blog". PyPy. Retrieved 12 May 2022.
  6. ^ "Assertion rewriting in Pytest part 1: Why it's needed". Python Insight. Retrieved 12 February 2022.
  7. ^ "Unit test basics". Microsoft. Retrieved 28 March 2022.
  8. ^ "About fixtures". Pytest. Retrieved 7 February 2022.
  9. ^ Ashwin, Pajankar. Python Unit Test Automation: Practical Techniques for Python Developers and Testers. Apress. ISBN 9781484226766. Retrieved 7 March 2022.
  10. ^ Molina, Alessandro (February 2021). Crafting Test-Driven Software with Python. Publisher(s): Packt Publishing. ISBN 9781838642655. Retrieved 8 March 2022.
  11. ^ Klein, Bernd. "Testing with Pytest". python-course.eu. Retrieved 5 March 2022.
  12. ^ "PyTest Marks". developers.perfectomobile.com. Retrieved 5 March 2022.
  13. ^ "Project examples". Pytest. Retrieved 1 February 2022.
  14. ^ Koorapati, Nipunn. "Open Sourcing Pytest Tools". Dropbox. Retrieved 1 February 2022.
  15. ^ Oliveira, Bruno (August 2018). pytest Quick Start Guide. Packt Publishing. ISBN 978-1-78934-756-2. Retrieved 1 February 2022.