Introduction to Pytest: Basics, Advanced Techniques, and Extensions
This article provides a comprehensive guide to Pytest, covering basic test case creation, exception handling, test classes, automatic test discovery, advanced features like parametrize, marks, fixtures, and plugin/hook development, complete with practical code examples and configuration tips.
All theory is for practice, so we start with a simple example: create a test file # pytest test_sample.py def inc(x): return x + 1 def test_answer(): assert inc(3) == 5 and run it with # pytest test_sample.py . The test fails because the assertion is wrong, demonstrating Pytest's basic rule that test modules and functions must be prefixed with test_ and use assert for verification.
Pytest also supports exception handling using with pytest.raises(...) :
<code># -*- coding:utf-8 -*-
# @Time : 2021-09-05
# @Author : Carl_DJ
import pytest
def f():
raise SystemExit(1)
def test_mytest():
# 捕获异常
with pytest.raises(SystemExit):
f()
</code>Test classes can be used to group tests, following the same test_ naming convention:
<code># -*- coding:utf-8 -*-
# @Time : 2021-09-05
# @Author : Carl_DJ
class TestClass:
# test_开头
def test_one(self):
x = "this"
assert "h" in x
# test_开头
def test_two(self):
x = "hello"
assert hasattr(x, "check")
</code>Running pytest in a directory containing multiple test scripts automatically discovers and executes all tests, enabling a "lazy" mode for batch testing.
Advanced techniques include parameterization, which allows a single test function to run with multiple inputs:
<code># 使用parametrize对测试用例参数化
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("'2'+'4'", "24"),
("6*9", 54)
])
def test_eval_1(test_input, expected):
assert eval(test_input) == expected
</code>Marks act as tags to include or exclude tests. For example, a slow test can be marked and later run or skipped via pytest.ini configuration:
<code># 标签
@pytest.mark.slow
def test_mark():
print("test mark")
time.sleep(10)
assert 5 == 5
</code> <code>[pytest]
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
</code>Running only fast tests: pytest test_sample.py -m "not slow" ; running only slow tests: pytest -m slow .
Fixtures provide reusable setup/teardown logic, similar to unittest but more powerful, and can be nested:
<code># -*- coding:utf-8 -*-
# @Time : 2021-09-05
# @Author : Carl_DJ
import pytest
@pytest.fixture
def first_entry():
return "a"
@pytest.fixture
def second_entry():
return 2
@pytest.fixture
def order(first_entry, second_entry):
return [first_entry, second_entry]
@pytest.fixture
def expected_list():
return ["a", 2, 3.0]
def test_string(order, expected_list):
order.append(3.0)
# 断言
assert order == expected_list
</code>Fixtures can also manage resources such as database connections by defining them in conftest.py .
Pytest can be extended with plugins and hooks. Creating a directory a with conftest.py and test_sub.py allows custom behavior:
<code># 在目录a下创建conftest.py
def pytest_runtest_setup(item):
# 在目录a下运行每个用例
print("setting up", item)
# 在目录a下创建test_sub.py
def test_sub():
pass
</code>Running pytest a/test_sub.py --capture=no displays the custom hook output.
In summary, Pytest offers simple test discovery, powerful parameterization, marking, fixtures, configuration via pytest.ini , and extensibility through plugins and hooks, making it a versatile tool for Python testing.
Python Programming Learning Circle
A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.