一文看懂pytest
基础概念
4A 级测试用例
以多米诺骨牌为例,一个完整且规范的的测试用例可以分为以下 4 个步骤
- Arrange:测试的准备工作,一般在 fixture 中进行定义。比如,找一块空闲的场地,把多米诺骨牌按照顺序和想要的形状摆好。
- Act:执行测试,改变被测对象的状态。比如,推到第一块多米诺骨牌。
- Assert:声明结果,观察被测对象的状态改变,并和我们的预期进行对比。比如,判断是否所有多米诺骨牌已经倒下,assert 全部倒下 == 成功
- Archive(Cleanup):测试后的清理工作,在 fixture 中使用 yield 语句实现。比如,把多米诺骨牌收起来,让场地回到最初的状态。
At its core, the test is ultimately the act and assert steps, with the arrange step only providing the context. Behavior exists between act and assert.
编写测试用例
1
2
3
4
5
6
# content of test_sample.py
def func(x):
return x + 1
def test_answer():
assert func(3) == 5
多个测试用例可以被归到一个测试类中,测试类需要以 Test
开头,对测试用例进行分类的好处有:
- 便于管理
- 可以在类的内部共享 fixture
- 批量使用 mark,即 mark 某个测试类便可以标记其内部所有的测试用例
1
2
3
4
5
6
7
8
9
# content of test_class.py
class TestClass:
def test_one(self):
x = "this"
assert "h" in x
def test_two(self):
x = "hello"
assert hasattr(x, "check")
执行测试用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# run tests in a module
pytest test_sample.py
# run tests in a dir
pytest testing/
# run tests by keywords, support logic operator: and, nor, or
pytest -k "answer and not method"
# run tests by ::
pytest tests/test_mod.py::test_func
# run tests by marker, e.g. @pytest.mark.slow
pytest -m slow
# run tests from package
pytest --pyargs pkg.testing
如果没有指定要执行的文件,默认情况下,pytest 将会执行当前目录及其子目录下所有以 test_
开头或以 _test
结尾的 python 文件。
从 8.2 版本开始,pytest 支持从文本文件中读取需要执行的参数,因此可以在单独的文件中管理复杂的测试序列。例如,以上所有的测试用例可以通过下面的语句进行执行:
1
pytest @tests_to_run.txt
文本文件中的内容如下:
1
2
3
4
5
6
test_sample.py
testing/
-k "answer and not method"
tests/test_mod.py::test_func
-m slow
--pyargs pkg.testing
fixture
pytest fixtures are designed to be explicit, modular and scalable.
fixture 是 pytest 最核心的概念,熟练掌握了 fixture,就等于熟练掌握了 pytest。
什么是 fixture
在测试中,fixture 提供了一个定义良好、可靠且一致的上下文环境,供测试使用。这可以包括环境(例如配置了已知参数的数据库)或内容(如数据集)。
Fixture 定义了测试用例的“初始化阶段(arrange phase)”的步骤和数据。它们也可以用于定义测试用例的“执行阶段(act phase)”,一般用于设计更复杂测试用例。
测试函数通过参数的方式访问由 fixture 设置的服务、状态或其他运行环境。一个测试函数可以接受和使用多个 fixture,fixture 也可以接受和使用其他 fixture。
当 pytest 执行一个测试用例时,它会查看该测试函数的参数,然后查找与这些参数同名的 fixture。一旦找到了对应的 fixture,pytest 就会执行这些 fixture,获取它们的返回值(如果有的话),并将这些返回的对象作为参数传入测试函数。
我们可以通过使用 @pytest.fixture
装饰器来告诉 pytest 某个函数是一个 fixture。下面是一个 Pytest fixture 的简单示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# contents of test_append.py
import pytest
# Arrange
@pytest.fixture
def first_entry():
return "a"
# Arrange
@pytest.fixture
def second_entry():
return 2
# Arrange, request 2 fixtures
@pytest.fixture
def order(first_entry, second_entry):
return [first_entry, second_entry]
# Arrange
@pytest.fixture
def expected_list():
return ["a", 2, 3.0]
def test_string(order, expected_list):
# Act
order.append(3.0)
# Assert
assert order == expected_list
复用 fixture
Pytest 的 fixture 之所以如此强大,其中一个原因是它允许我们定义一个通用的 setup 步骤,并像普通函数一样被重复使用。不同的测试可以请求相同的 fixture,而 pytest 会为每个测试提供该 fixture 的独立结果,确保测试彼此之间互不影响。我们可以利用这个机制,确保每个测试都能获取一份全新的数据,并从干净的初始状态开始,从而产生一致且可复现的结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# contents of test_append.py
import pytest
# Arrange
@pytest.fixture
def first_entry():
return "a"
# Arrange
@pytest.fixture
def order(first_entry):
return [first_entry]
def test_string(order):
# Act
order.append("b")
# Assert
assert order == ["a", "b"]
def test_int(order):
# Act
order.append(2)
# Assert
assert order == ["a", 2]
自动使用 fixture
有时,你可能希望某个 fixture(甚至是多个)被所有测试自动使用。这时,“自动使用(autouse)”的 fixture 就非常方便,它能让所有测试自动请求这些 fixture,从而省去大量重复的声明,并能实现更高级的 fixture 用法。
我们可以通过在 fixture 的装饰器中传入 autouse=True
来将其设为自动使用的 fixture。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# contents of test_append.py
import pytest
@pytest.fixture
def first_entry():
return "a"
@pytest.fixture
def order(first_entry):
return []
@pytest.fixture(autouse=True)
def append_first(order, first_entry):
return order.append(first_entry)
def test_string_only(order, first_entry):
assert order == [first_entry]
def test_string_and_int(order, first_entry):
order.append(2)
assert order == [first_entry, 2]
fixture 作用域
需要网络访问的 fixture 往往依赖于网络连接,并且创建成本较高、耗时较长。我们可以为 @pytest.fixture
添加一个参数 scope="module"
,这样 smtp_connection fixture 函数就只会在每个测试模块中执行一次(默认是每个测试函数执行一次)。
这样,同一个测试模块中的多个测试函数就能共享同一个 smtp_connection 实例,从而节省时间。
scope 参数可选的取值包括:
- function:默认作用域,fixture 在每个测试函数执行完毕后被销毁。
- class:fixture 在当前类中最后一个测试用例结束时销毁。
- module:fixture 在当前模块中最后一个测试用例结束时销毁。
- package:fixture 在定义该 fixture 的包(包括其子包和子目录)中最后一个测试用例结束时销毁。
- session:fixture 会在整个测试会话结束时销毁(即只创建一次,贯穿所有测试模块)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# content of conftest.py
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp_connection():
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
# content of test_module.py
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
assert 0 # for demo purposes
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
assert 0 # for demo purposes
在某些情况下,你可能希望更改 fixture 的作用域,但又不想修改代码。这时,你可以将一个可调用对象(callable)传递给 scope 参数。这个可调用对象必须返回一个字符串,表示一个有效的作用域名称,并且它只会在 fixture 定义时执行一次。
该 callable 会接收两个关键字参数:
- fixture_name:一个字符串,表示当前 fixture 的名称;
- config:一个配置对象(pytest 的配置实例)。
这种方式在处理需要较长初始化时间的 fixture 时尤其有用,例如启动一个 Docker 容器。你可以通过命令行参数控制这些容器在不同环境中的作用域设置。
1
2
3
4
5
6
7
8
9
def determine_scope(fixture_name, config):
if config.getoption("--keep-containers", None):
return "session"
return "function"
@pytest.fixture(scope=determine_scope)
def docker_container():
yield spawn_container()
清理机制 Yield 和 Return
当你在 fixture 中用 return 返回一个值时,fixture 会在执行到 return 的地方直接结束,然后把返回值传给测试函数。这种方式适用于只需要简单提供一个资源或数据,不需要后续清理操作的场景。
我们希望测试用例能够自行清理环境,避免干扰其他测试用例,同时也避免留下大量测试数据,导致系统臃肿。pytest 的 fixture 提供了一个非常有用的清理(teardown)机制,允许我们为每个 fixture 定义具体的清理步骤。
最常用的清理机制是使用 yield 形式的 fixture,即使用 yield 替代 return。它能把 fixture 分成两部分:
- yield 前面的代码是 setup(准备),在测试调用之前执行。
- yield 后面的代码是 teardown(清理),在测试执行完毕后执行。
pytest 会确定 fixture 执行的线性顺序,依次运行每个 fixture,直到执行到 return 或 yield,然后继续执行下一个 fixture。当测试执行结束后,pytest 会以相反的顺序,依次执行每个通过 yield 返回过对象的 fixture 中 yield 语句之后的代码,实现清理功能。
以一个简单的电子邮件收发场景为例,我们想要测试如下的电子邮件模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# content of emaillib.py
class MailAdminClient:
def create_user(self):
return MailUser()
def delete_user(self, user):
# do some cleanup
pass
class MailUser:
def __init__(self):
self.inbox = []
def send_email(self, email, other):
other.inbox.append(email)
def clear_mailbox(self):
self.inbox.clear()
class Email:
def __init__(self, subject, body):
self.subject = subject
self.body = body
假设我们想测试从一个用户向另一个用户发送邮件。我们首先需要创建两个用户,然后让一个用户发送邮件给另一个用户,最后断言对方收到了该邮件并显示在收件箱中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# content of test_emaillib.py
from emaillib import Email, MailAdminClient
import pytest
@pytest.fixture
def mail_admin():
return MailAdminClient()
@pytest.fixture
def sending_user(mail_admin):
user = mail_admin.create_user()
yield user
mail_admin.delete_user(user)
@pytest.fixture
def receiving_user(mail_admin):
user = mail_admin.create_user()
yield user
user.clear_mailbox()
mail_admin.delete_user(user)
def test_email_received(sending_user, receiving_user):
email = Email(subject="Hey!", body="How's it going?")
sending_user.send_email(email, receiving_user)
assert email in receiving_user.inbox
由于 receiving_user 是在设置阶段最后一个执行的 fixture,所以在清理(teardown)阶段它会最先执行。
安全清理机制 Safe Teardown
需要注意的是,如果一个使用 yield 的 fixture 在执行到 yield 之前抛出了异常,pytest 不会尝试执行该 fixture 中 yield 语句之后的清理代码。
下面代码的代码重写了上面的测试用例,这个版本虽然更加紧凑,但也更难阅读,fixture 的名称不够具有描述性,而且这些 fixture 也很难被重复使用。更严重的问题是,如果设置(setup)阶段的任何一个步骤抛出异常,后续的清理(teardown)代码都不会被执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# content of test_emaillib.py
from emaillib import Email, MailAdminClient
import pytest
@pytest.fixture
def setup():
mail_admin = MailAdminClient()
sending_user = mail_admin.create_user()
receiving_user = mail_admin.create_user()
email = Email(subject="Hey!", body="How's it going?")
sending_user.send_email(email, receiving_user)
yield receiving_user, email
receiving_user.clear_mailbox()
mail_admin.delete_user(sending_user)
mail_admin.delete_user(receiving_user)
def test_email_received(setup):
receiving_user, email = setup
assert email in receiving_user.inbox
最安全、最简洁的 fixture 结构是:每个 fixture 只负责一个会改变状态(state-changing)的操作,然后将其与对应的清理逻辑配对,就像最开始的邮件示例中展示的那样。
通常,状态变更操作在失败时还能成功修改状态的几率是极低的,因为这些操作往往是 transaction-based。因此,如果我们确保每一个成功的状态变更操作都有对应的清理逻辑,并且将其与可能失败的其他操作拆分为独立的 fixture 函数,就能最大限度地保障测试完成后环境恢复如初。
举个例子,假设我们有一个带登录页面的网站,并且能通过管理员 API 创建用户。我们打算进行如下测试流程:
- 通过管理员 API 创建一个用户
- 使用 Selenium 启动一个浏览器
- 访问我们网站的登录页面
- 用刚才创建的用户登录
- 断言该用户的名字出现在登录后页面的页眉中
我们当然不希望这个测试留下那个测试用户,也不希望留下浏览器会话,所以必须确保用于创建这些内容的 fixture 能自动清理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from uuid import uuid4
from urllib.parse import urljoin
from selenium.webdriver import Chrome
import pytest
from src.utils.pages import LoginPage, LandingPage
from src.utils import AdminApiClient
from src.utils.data_types import User
@pytest.fixture
def admin_client(base_url, admin_credentials):
return AdminApiClient(base_url, **admin_credentials)
@pytest.fixture
def user(admin_client):
_user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$$word")
admin_client.create_user(_user)
yield _user
admin_client.delete_user(_user)
@pytest.fixture
def driver():
_driver = Chrome()
yield _driver
_driver.quit()
@pytest.fixture
def login(driver, base_url, user):
driver.get(urljoin(base_url, "/login"))
page = LoginPage(driver)
page.login(user)
@pytest.fixture
def landing_page(driver, login):
return LandingPage(driver)
def test_name_on_landing_page_after_login(landing_page, user):
assert landing_page.header == f"Welcome, {user.name}!"
由于 fixture 之间的依赖关系是如何排列的并不明确,所以我们无法确定 user fixture 是否会在 driver fixture 之前执行。但这没关系,因为这两个操作都是原子性的,也就是说它们的执行顺序无关紧要,测试的整体执行流程仍然是可以线性化的(即行为是确定、可推理的)。
但真正重要的是:无论哪个 fixture 先执行,如果其中一个抛出异常,而另一个没有抛出异常,那么两者都不会留下任何残留。
安全运行多个断言(assert)
有时,在完成所有设置之后,你可能希望运行多个断言(assert)且无需重复前面的准备步骤。Pytest 提供了一个非常方便的方式来处理这种情况:
- 设置一个更大的 fixture 作用域(scope),例如 class;
- 将 act 步骤定义为 autouse fixture;
- 确保所有相关的 fixture 都应用于那个更大的作用域。
我们来对前面的一个示例稍作修改。假设除了检查欢迎信息是否出现在页眉中之外,我们还想检查:是否有“登出”按钮;是否有指向用户个人主页的链接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# contents of tests/end_to_end/test_login.py
from uuid import uuid4
from urllib.parse import urljoin
from selenium.webdriver import Chrome
import pytest
from src.utils.pages import LoginPage, LandingPage
from src.utils import AdminApiClient
from src.utils.data_types import User
@pytest.fixture(scope="class")
def admin_client(base_url, admin_credentials):
return AdminApiClient(base_url, **admin_credentials)
@pytest.fixture(scope="class")
def user(admin_client):
_user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$$word")
admin_client.create_user(_user)
yield _user
admin_client.delete_user(_user)
@pytest.fixture(scope="class")
def driver():
_driver = Chrome()
yield _driver
_driver.quit()
@pytest.fixture(scope="class")
def landing_page(driver, login):
return LandingPage(driver)
class TestLandingPageSuccess:
@pytest.fixture(scope="class", autouse=True)
def login(self, driver, base_url, user):
driver.get(urljoin(base_url, "/login"))
page = LoginPage(driver)
page.login(user)
def test_name_in_header(self, landing_page, user):
assert landing_page.header == f"Welcome, {user.name}!"
def test_sign_out_button(self, landing_page):
assert landing_page.sign_out_button.is_displayed()
def test_profile_link(self, landing_page, user):
profile_href = urljoin(base_url, f"/profile?id={user.profile_id}")
assert landing_page.profile_link.get_attribute("href") == profile_href
这些方法虽然在函数签名中引用了 self,但这仅仅是形式上的,实际上并没有将状态绑定到测试类本身。所有的状态和依赖管理都由 pytest 的 fixture 负责。每个方法只需要请求它真正需要的 fixture,不必关心执行顺序。这是因为 login 操作是一个 autouse fixture,它会确保在自己运行之前,所有其它依赖的 fixture(drive, user) 都已经执行完毕。
测试执行时不再需要进行额外的状态变更操作,因此可以自由地执行任意多的“只读查询”操作,而不会影响其他测试,也不会被其他测试影响。
login fixture 被定义在测试类内部,是因为并不是模块中的所有测试都需要登录成功。例如,如果我们想要写一个新的测试场景,来测试登录密码出错的情况,我们可以在测试文件中添加下面的内容:
1
2
3
4
5
6
7
8
9
10
class TestLandingPageBadCredentials:
@pytest.fixture(scope="class")
def faux_user(self, user):
_user = deepcopy(user)
_user.password = "badpass"
return _user
def test_raises_bad_credentials_exception(self, login_page, faux_user):
with pytest.raises(BadCredentialsException):
login_page.login(faux_user)
使用 markers 给 fixture 传递数据
通过使用 request 对象,fixture 还可以访问应用在测试函数上的标记(marker)。这对于从测试函数向 fixture 传递数据非常有用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@pytest.fixture
def fixt(request):
marker = request.node.get_closest_marker("fixt_data")
if marker is None:
# Handle missing marker in some way...
data = None
else:
data = marker.args[0]
# Do something with the data
return data
@pytest.mark.fixt_data(42)
def test_fixt(fixt):
assert fixt == 42
Factories as fixtures
当一个 fixture 的结果在单个测试中需要多次使用时,可以让 fixture 返回一个用于生成数据的函数。在测试中多次调用这个函数,从而获得多个独立的数据实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@pytest.fixture
def make_customer_record():
created_records = []
def _make_customer_record(name):
record = models.Customer(name=name, orders=[])
created_records.append(record)
return record
yield _make_customer_record
for record in created_records:
record.destroy()
def test_customer_records(make_customer_record):
customer_1 = make_customer_record("Lisa")
customer_2 = make_customer_record("Mike")
customer_3 = make_customer_record("Meredith")
参数化 fixture
fixture 函数是可以参数化的。在这种情况下,它们会被调用多次,每次调用都会执行依赖该 fixture 的测试函数。这些测试函数通常不需要关心自己被重复执行了多少次。
fixture 参数化对于那些可以通过多种方式进行配置的组件来说非常有用,它有助于我们编写覆盖全面的功能测试。
我们可以为某个 fixture 添加参数,比如创建两个不同的 smtp_connection 实例。这样,所有使用该 fixture 的测试就会运行两次,每次使用不同的参数。在参数化的 fixture 函数中,可以通过特殊的 request 对象来访问每一个参数:
1
2
3
4
5
6
@pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"])
def smtp_connection(request):
smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
yield smtp_connection
print(f"finalizing {smtp_connection}")
smtp_connection.close()
通过 fixture 参数化对测试用例自动分组
在测试运行期间,pytest 会尽量减少同时 active 的 fixture 数量。如果你使用了参数化的 fixture,那么所有依赖它的测试会先使用一个参数值执行完毕,在创建下一个 fixture 实例之前会先调用清理(finalizer)逻辑。
下面的示例展示了两个参数化的 fixture,其中一个使用的是模块级作用域,所有函数中都使用了 print 来演示 setup/teardown 的执行顺序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# content of test_module.py
import pytest
@pytest.fixture(scope="module", params=["mod1", "mod2"])
def modarg(request):
param = request.param
print(" SETUP modarg", param)
yield param
print(" TEARDOWN modarg", param)
@pytest.fixture(scope="function", params=[1, 2])
def otherarg(request):
param = request.param
print(" SETUP otherarg", param)
yield param
print(" TEARDOWN otherarg", param)
def test_0(otherarg):
print(" RUN test0 with otherarg", otherarg)
def test_1(modarg):
print(" RUN test1 with modarg", modarg)
def test_2(otherarg, modarg):
print(f" RUN test2 with otherarg {otherarg} and modarg {modarg}")
运行测试后可以看到,模块级作用域的参数化 modarg 会影响测试用例的执行顺序,使得在任意时刻active 的 fixture 数量最少。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
test_module.py::test_0[1] SETUP otherarg 1
RUN test0 with otherarg 1
PASSED TEARDOWN otherarg 1
test_module.py::test_0[2] SETUP otherarg 2
RUN test0 with otherarg 2
PASSED TEARDOWN otherarg 2
test_module.py::test_1[mod1] SETUP modarg mod1
RUN test1 with modarg mod1
PASSED
test_module.py::test_2[mod1-1] SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod1
PASSED TEARDOWN otherarg 1
test_module.py::test_2[mod1-2] SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod1
PASSED TEARDOWN otherarg 2
test_module.py::test_1[mod2] TEARDOWN modarg mod1
SETUP modarg mod2
RUN test1 with modarg mod2
PASSED
test_module.py::test_2[mod2-1] SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod2
PASSED TEARDOWN otherarg 1
test_module.py::test_2[mod2-2] SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod2
PASSED TEARDOWN otherarg 2
TEARDOWN modarg mod2
usefixtures
直接传入 fixture 作为测试函数参数,fixture 的返回值可以在测试函数中使用。但有时,测试函数并不需要直接访问 fixture 的返回值。例如,测试可能只要求在一个空目录下作为当前工作目录(current working directory)运行,但并不关心这个目录的具体路径。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# content of conftest.py
import os
import tempfile
import pytest
@pytest.fixture
def cleandir():
with tempfile.TemporaryDirectory() as newpath:
old_cwd = os.getcwd()
os.chdir(newpath)
yield
os.chdir(old_cwd)
# content of test_setenv.py
import os
import pytest
# declare its use in a test module via a usefixtures marker
@pytest.mark.usefixtures("cleandir")
class TestDirectoryInit:
def test_cwd_starts_empty(self):
assert os.listdir(os.getcwd()) == []
with open("myfile", "w", encoding="utf-8") as f:
f.write("hello")
def test_cwd_again_starts_empty(self):
assert os.listdir(os.getcwd()) == []
使用 @pytest.mark.usefixtures("cleandir")
,pytest 会执行 cleandir 的 setup 和 teardown,但不会把返回值传给测试函数,这样可以保持测试函数的签名简洁。
Overriding fixture
在一个相对较大的测试套件中,你很可能需要用局部定义的 fixture 来覆盖全局的 fixture,以此来保持测试代码的可读性和可维护性。
使用其它项目的 fixture
通常,支持 pytest 的项目会通过 entry points 来注册 fixture,因此只需将这些项目安装到环境中,它们的 fixture 就会自动可用。如果你想要使用的 fixture 所在的项目没有使用 entry points,你可以在你项目的最上层 conftest.py 文件中定义 pytest_plugins,将目标模块注册为 Pytest 插件。
比如,你有一些 fixture 定义在 mylibrary.fixtures 模块中,现在你希望在你的 app/tests 目录下的测试中复用它们。你只需要在 app/tests/conftest.py 中添加如下内容:
1
pytest_plugins = "mylibrary.fixtures"
built-in fixtures
输入 pytest --fixtures
可以展示所有内置的 fixture。
marks
pytest 中主要有两类函数
- fixture 函数:函数声明上有
@pytest.fixture
修饰符 - 测试函数:以
test_
开头,接收 fixture 作为函数参数,可以使用@pytest.mark
修饰符