Python's most popular third-party open source testing framework-pytest

1. Introduction

This article introduces the most popular third-party unit testing framework in the Python world: pytest.
It has the following main features:

  • Details assert output when the assertion fails (no longer have to remember self.assert*the name of)
  • Automatically discover test modules and functions
  • Modular fixture to manage various test resources
  • Of unittestfully compatible for nosebasic compatibility
  • Very rich plug-in system, there are more than 315 third-party plug-ins, the community is prosperous

And described earlier unittestand nosethe same, we will introduce several aspects of the following pytestcharacteristics.

2. Use case writing

The same noseas pytestsupport functions, test class in the form of test cases. The biggest difference is that you can enjoy using the assertstatement asserted, did not worry it will noseor unittestlack of problems arising in the context of detailed information.

For example the following test example, so deliberately test_upperasserted not by:

import pytest

def test_upper():
    assert 'foo'.upper() == 'FOO1'

class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        with pytest.raises(TypeError):
            x + []

Whereas when pytestthe time to execute the use cases, it outputs detailed (and a variety of colors) context information:

=================================== test session starts ===================================
platform darwin -- Python 3.7.1, pytest-4.0.1, py-1.7.0, pluggy-0.8.0
rootdir: /Users/prodesire/projects/tests, inifile:
plugins: cov-2.6.0
collected 3 items

test.py F..                                                                         [100%]

======================================== FAILURES =========================================
_______________________________________ test_upper ________________________________________

    def test_upper():
>       assert 'foo'.upper() == 'FOO1'
E       AssertionError: assert 'FOO' == 'FOO1'
E         - FOO
E         + FOO1
E         ?    +

test.py:4: AssertionError
=========================== 1 failed, 2 passed in 0.08 seconds ============================

It is not difficult to see that pytestboth the test code context and the value of the measured variable are output. Compared to noseand unittest, pytestallow users an easier way to write test cases, but also get a richer and friendly test results.

Three, use case discovery and execution

unittestAnd nosesupported use cases discovered and implementation capacity, pytestare supported. pytestSupport automatic (recursive) discovery of use cases:

  • Default found that all comply with the current directory test_*.pyor *_test.pythe test case file to testtest the function at the beginning or at Testthe beginning of the test class with testtest methods at the beginning of
  • Use pytestthe command
  • With nose2the same concept, by specifying certain parameters in the configuration file, the name of the model can be configured with the file cases, classes and functions (fuzzy matching)

pytest It also supports the execution of specified use cases:

  • Specify test file path
    • pytest /path/to/test/file.py
  • Specify test class
    • pytest /path/to/test/file.py:TestCase
  • Specify test method
    • pytest another.test::TestClass::test_method
  • Specify test function
    • pytest /path/to/test/file.py:test_function

Four, test fixtures (Fixtures)

pytestThe test fixture and unittest, nose, nose2of different styles, it can not only achieve setUpand tearDownthis pre-testing and clean-up logic, but also a lot of other powerful features.

4.1 Declaration and use

pytestThe test fixture in is more like a test resource, you only need to define a fixture, and then you can use it directly in the use case. Thanks to pytestthe dependency injection mechanism, you do not need to pass from xx import xxin the form of display to import only need to specify the same name as the parameter to the function of the test parameters, such as:

import pytest

@pytest.fixture
def smtp_connection():
    import smtplib

    return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)

def test_ehlo(smtp_connection):
    response, msg = smtp_connection.ehlo()
    assert response == 250

In the above example defines a test fixture smtp_connection, the test functions test_ehlodefined parameters of the same name in the signature, the pytestframe is automatically injected into the variable.

4.2 Sharing

In pytest, the multiple test cases can be a test fixture with a plurality of test file sharing. Simply package (Package) defined in the conftest.pydocument, and defines the test fixture written in the file, then the packet within all modules (Module) may be used in all the test cases conftest.pyin the test fixture as defined herein.

For example, if the following file structure test_1/conftest.pydefines the test jig, then test_a.py, and test_b.pyyou can use the test fixture; and test_c.pycan not be used.

`-- test_1
|   |-- conftest.py
|   `-- test_a.py
|   `-- test_b.py
`-- test_2
    `-- test_c.py

4.3 Effective level

unittestAnd nosesupport pre-testing and cleanup of entry into force level: test methods, test classes and test modules.

pytestThe test fixture also supports various effective levels and is more abundant. Pytest.fixture by specifying the scopeset parameters:

  • function —— function level, that is, before calling each test function, fixtures will be regenerated
  • class —— Class level, fixture will be regenerated before calling each test class
  • module-module level, fixtures will be regenerated before loading each test module
  • package —— package level, fixture will be regenerated before loading each package
  • session —— Session level, before running all use cases, only generate fixtures once

When we specify the effective level as the module level, the example is as follows:

import pytest
import smtplib

@pytest.fixture(scope="module")
def smtp_connection():
    return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)

4.4 Test pre-processing and cleaning

pytestThe test fixture can be realized pre-testing and cleaning through yieldto split the two logical statements, writing becomes very simple, such as:

import smtplib
import pytest

@pytest.fixture(scope="module")
def smtp_connection():
    smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
    yield smtp_connection  # provide the fixture value
    print("teardown smtp")
    smtp_connection.close()

In the above example, yield smtp_connectionand the previous statement is equivalent to the pre-test, the yieldreturned resources prepared test smtp_connection; while the later statement will be executed at the end with the embodiment (to be exact end effective level when the lifecycle test fixture) after Execution is equivalent to test cleanup.

If the generation of test resources (such as in the example smtp_connection) process support withstatement, you can also write a simpler form:

@pytest.fixture(scope="module")
def smtp_connection():
    with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
        yield smtp_connection  # provide the fixture value

pytest In addition to the functions described in the article, the test fixture of the test fixture also has more high-level gameplay such as parameterized fixtures, factory fixtures, and fixtures in fixtures.

Five, skip the test and expected failure

pytestIn addition to supporting unittestand nosetestskip the test and is expected to fail the way, but also pytest.markprovides a corresponding method:

  • Skip the test directly through skip decorator or pytest.skip function
  • Skip the test conditionally through skipif
  • Expect test failure through xfail

Examples are as follows:

@pytest.mark.skip(reason="no way of currently testing this")
def test_mark_skip():
    ...

def test_skip():
    if not valid_config():
        pytest.skip("unsupported configuration")

@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher")
def test_mark_skip_if():
    ...

@pytest.mark.xfail
def test_mark_xfail():
    ...

Six, sub-test/parameterized test

pytestIn addition to supporting unittestthe TestCase.subTest, also supports a more flexible test written in a way the child, that is 参数化测试, through the pytest.mark.parametrizerealization of a decorator.

In the following example, define a test_evaltest function by pytest.mark.parametrizethe specified set of parameters decorators 3, three sub-tests will be generated:

@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
    assert eval(test_input) == expected

In the example, the last set of parameters is deliberately caused to fail. Run the use case to see the rich test result output:

========================================= test session starts =========================================
platform darwin -- Python 3.7.1, pytest-4.0.1, py-1.7.0, pluggy-0.8.0
rootdir: /Users/prodesire/projects/tests, inifile:
plugins: cov-2.6.0
collected 3 items

test.py ..F                                                                                     [100%]

============================================== FAILURES ===============================================
__________________________________________ test_eval[6*9-42] __________________________________________

test_input = '6*9', expected = 42

    @pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
    def test_eval(test_input, expected):
>       assert eval(test_input) == expected
E       AssertionError: assert 54 == 42
E        +  where 54 = eval('6*9')

test.py:6: AssertionError
================================= 1 failed, 2 passed in 0.09 seconds ==================================

If the parameters are replaced pytest.param, we can also have higher-level gameplay, such as knowing that the last set of parameters failed, so mark it as xfail:

@pytest.mark.parametrize(
    "test_input,expected",
    [("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
)
def test_eval(test_input, expected):
    assert eval(test_input) == expected

If the values ​​of multiple parameters of the test function want to be arranged and combined with each other, we can write:

@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    pass

In the above-described example will be, respectively x=0/y=2, x=1/y=2, x=0/y=3and x=1/y=3into the test functions, considered four test performed.

Seven, test result output

pytestThe test results are compared to the output unittestand nosericher, its advantages:

  • Highlight output, pass or fail will use different colors to distinguish
  • Richer context information, automatically output code context and variable information
  • Test progress display
  • Test result output layout is more friendly and easy to read

If you exchange experience in software testing, interface testing, automated testing, and interviews. If you are interested, you can add software test communication: 1085991341, and there will be technical exchanges with colleagues.

8. Plug-in system

pytest The plug-in is very rich, and plug and play, as a user does not need to write additional code.

In addition, thanks to pytestgood architecture design and hook mechanism, it becomes easy to write plug-ins to get started.

Nine, summary

The three introductions to the Python testing framework are coming to an end here. After writing so much, I am afraid that the judges are also tired. We might as well list a horizontal comparison table to summarize the similarities and differences of these unit test frameworks:

unittest nose nose2 pytest
Automatic discovery of use cases
Specify (at each level) use case execution
Support assert assertion weak weak weak Strong
Test Fixture
Test fixture type Pre-processing and cleaning Pre-processing and cleaning Pre-processing and cleaning Preposition, cleanup, built-in fixtures, custom fixtures
Test fixture effective level Methods, classes, modules Methods, classes, modules Methods, classes, modules Methods, classes, modules, packages, sessions
Support skip test and predict failure
Subtest
Test result output general better better it is good
Plug-in - Richer general rich
hook - -
Community ecology As a standard library, officially maintained Stop maintenance Under maintenance, low activity Under maintenance, high activity

Python's unit testing frameworks seem to be of a wide variety, but they are actually an evolution from generation to generation, with traces to follow. Grasping its characteristics, combined with the use of the scene, you can easily make a choice.

If you do not want or are not allowed to install third-party libraries, it unittestis the best and only choice. On the other hand, pytestit is undoubtedly the best choice for many open-source projects are using Python pytestas a unit testing framework.
The above content is the entire content of this article. The above content hopes to be helpful to you. Friends who have been helped are welcome to like and comment.

Guess you like

Origin blog.csdn.net/Chaqian/article/details/106620008