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
unittest
fully compatible fornose
basic compatibility - Very rich plug-in system, there are more than 315 third-party plug-ins, the community is prosperous
And described earlier unittest
and nose
the same, we will introduce several aspects of the following pytest
characteristics.
2. Use case writing
The same nose
as pytest
support functions, test class in the form of test cases. The biggest difference is that you can enjoy using the assert
statement asserted, did not worry it will nose
or unittest
lack of problems arising in the context of detailed information.
For example the following test example, so deliberately test_upper
asserted 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 pytest
the 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 pytest
both the test code context and the value of the measured variable are output. Compared to nose
and unittest
, pytest
allow users an easier way to write test cases, but also get a richer and friendly test results.
Three, use case discovery and execution
unittest
And nose
supported use cases discovered and implementation capacity, pytest
are supported. pytest
Support automatic (recursive) discovery of use cases:
- Default found that all comply with the current directory
test_*.py
or*_test.py
the test case file totest
test the function at the beginning or atTest
the beginning of the test class withtest
test methods at the beginning of - Use
pytest
the command - With
nose2
the 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)
pytest
The test fixture and unittest
, nose
, nose2
of different styles, it can not only achieve setUp
and tearDown
this pre-testing and clean-up logic, but also a lot of other powerful features.
4.1 Declaration and use
pytest
The 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 pytest
the dependency injection mechanism, you do not need to pass from xx import xx
in 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_ehlo
defined parameters of the same name in the signature, the pytest
frame 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.py
document, 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.py
in the test fixture as defined herein.
For example, if the following file structure test_1/conftest.py
defines the test jig, then test_a.py
, and test_b.py
you can use the test fixture; and test_c.py
can not be used.
`-- test_1
| |-- conftest.py
| `-- test_a.py
| `-- test_b.py
`-- test_2
`-- test_c.py
4.3 Effective level
unittest
And nose
support pre-testing and cleanup of entry into force level: test methods, test classes and test modules.
pytest
The test fixture also supports various effective levels and is more abundant. Pytest.fixture by specifying the scope
set 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
pytest
The test fixture can be realized pre-testing and cleaning through yield
to 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_connection
and the previous statement is equivalent to the pre-test, the yield
returned 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 with
statement, 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
pytest
In addition to supporting unittest
and nosetest
skip the test and is expected to fail the way, but also pytest.mark
provides 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
pytest
In addition to supporting unittest
the TestCase.subTest
, also supports a more flexible test written in a way the child, that is 参数化测试
, through the pytest.mark.parametrize
realization of a decorator.
In the following example, define a test_eval
test function by pytest.mark.parametrize
the 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=3
and x=1/y=3
into the test functions, considered four test performed.
Seven, test result output
pytest
The test results are compared to the output unittest
and nose
richer, 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 pytest
good 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:
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 unittest
is the best and only choice. On the other hand, pytest
it is undoubtedly the best choice for many open-source projects are using Python pytest
as 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.