Author: HelloGitHub- Prodesire
HelloGitHub of "explain open source projects" series, Project address: https: //github.com/HelloGitHub-Team/Article
I. INTRODUCTION
This article is the third "talk Python unit testing framework", the first two were introduced standard library unittest and third-party unit test framework nose. As the last article in this series, the finale is the Python world's hottest third-party unit test framework: pytest.
pytest Project address: https: //github.com/pytest-dev/pytest
It has the following main features:
- assert output detailed information assertion failure (no longer have to remember
self.assert*
the name of) - Auto discovery test modules and functions
- Modular fixture for managing various types of test resources
- Of
unittest
fully compatible fornose
basic compatibility - Very rich plug-in system, there are more than 315 models third-party plug-ins , community prosperity
And described earlier unittest
and nose
the same, we will introduce several aspects of the following pytest
characteristics.
Second, prepared by Example
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 ============================
Easy to see, pytest
only the output of the test code context information also measured output variable values. Compared to nose
and unittest
, pytest
allow users an easier way to write test cases, but also get a richer and friendly test results.
Third, execution and use were found
unittest
And nose
supported use cases discovered and implementation capacity, pytest
are supported.
pytest
Automatic support (recursively) found with Example:
- 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
- Use
- With
nose2
the same idea, by the configuration file to specify specific parameters, the model can be configured with the name of the file cases, classes and functions (fuzzy matching)
pytest
Also support the implementation of the specified use cases:
- Specifies the test file path
pytest /path/to/test/file.py
- Specify the test class
pytest /path/to/test/file.py:TestCase
- Specify test methods
pytest another.test::TestClass::test_method
- Specifies the test function
pytest /path/to/test/file.py:test_function
Fourth, the test fixture (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 is more like test resources, you only need to define a fixture, then it can be used directly in the use examples. 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 Share
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 take effect 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 is also support a variety of levels to take effect, and more abundant. By pytest.fixture specify the scope
set parameters:
- function - before the function level, that calls each test function, will regenerate fixture
- class - class level, before calling each test class, will regenerate fixture
- module - the module level, before loading each test module, will regenerate fixture
- package - package level, before loading each package, will regenerate fixture
- session - before the session level, to run all use cases, only generated once fixture
When we specify take effect level module level, for example:
import pytest
import smtplib
@pytest.fixture(scope="module")
def smtp_connection():
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
4.4 Pre-testing and clean-up
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, equivalent 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
The paper introduces the test fixture addition to these functions, as well as the parameters of the fixture , factory clamps , using a jig in the jig and other higher-order play more details, please read "pytest fixtures: Explicit, Modular, Scalable" .
Fifth, and is expected to skip the test fails
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:
- By skip decorative or pytest.skip skip direct function test
- By skipif skipping conditional test
- By xfail test is expected to fail
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():
...
More games are played on and is expected to skip the test fails, see "Skip and xfail: dealing with tests that can not succeed"
Six sub-test / parametric 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
The last example deliberately set of parameters lead to failure, run rich with examples you can see the output of the test results:
========================================= 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 parameters changed pytest.param
, we can also have a higher order of play, such as know the last set of parameters is a failure, 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 value of multiple parameters of test functions hope permutation and combination with each other, so 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, will not be distinguished by different colors or by
- Richer context information, the context output code and automatically variable information
- Tests show progress
- Test results output layout more user-friendly and easy to read
Eight, plug-in system
pytest
The plug-in is very rich, and plug and play, as users do not need to write additional code. About the use of plug-ins, see "Installing and the Using plugins" .
In addition, thanks to pytest
good architecture design and hook mechanism, it becomes easy to write plug-ins to get started. Write about plug-ins, see "Writing plugins" .
Nine, summary
About three Python testing frameworks introduced here would winding down. Write so much, afraid Tell me what you see is tired. We may wish to list a horizontal comparison table summarizes the similarities and differences to the frame unit testing:
unittest | nose | nose2 | pytest | |
---|---|---|---|---|
Auto discovery use cases | ✔ | ✔ | ✔ | ✔ |
Specified (all levels) with Example execution | ✔ | ✔ | ✔ | ✔ |
Support assert assertion | weak | weak | weak | Strong |
Test Fixture | ✔ | ✔ | ✔ | ✔ |
The type of test fixture | Front and cleanup | Front and cleanup | Front and cleanup | Front, clean up, all kinds of built-in fixtures, all kinds of custom fixtures |
Test fixture into effect level | Methods, classes, modules | Methods, classes, modules | Methods, classes, modules | Methods, classes, modules, packages, session |
Support is expected to skip the test and fail | ✔ | ✔ | ✔ | ✔ |
Subtest | ✔ | ✔ | ✔ | ✔ |
Test results output | general | better | better | it is good |
Plug | - | Rich | general | rich |
hook | - | - | ✔ | ✔ |
Community Ecology | As a standard library, maintained by the official | Stop Maintenance | Maintenance, low activity | Maintenance, active high |
Python unit testing framework seemingly wide range of generations of evolution but in reality is, there is a pattern. Seize its characteristics, combined with 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. Conversely, pytest
is the best choice, many Python open source projects (such as the famous Requests ) are used pytest
as the unit testing framework. Indeed, even nose2
in official documents on both suggest that you use pytest
, this is too much admiration ah!
"Explain open source projects Series" - to let people interested in open source projects are no longer afraid, let sponsors open source projects are no longer alone. Follow our articles, you'll discover the fun of programming, the use of open source projects and found to be involved so simple. Welcome messages to contact us, join us, so that more people fall in love with open source, open source contribution ~