Fixture函数:清晰 模块化 易扩展

2.0/2.3/2.4版本新函数

text Fixture函数的目的是为测试的重复执行提供一个可靠的固定基线。Fixture函数比经典的xUnit setUp/tearDown方法有着显着的改进:

  • Fixture函数具有明确的名称,在测试用例/类/模块或整个项目中通过声明使用的Fixture函数名称来使用。

  • Fixture函数以模块化方式实现,因为每个Fixture名称都会触发调用Fixture函数,该fixture函数本身可以使用其它的Fixture函数。

  • 从简单的单元测试到复杂的函数测试,Fixture函数的管理允许根据配置和组件选项对Fixture函数和测试用例进行参数化,或者在测试用例/类/模块或整个测试会话范围内重复使用该Fixture函数。

此外,Pytest继续支持经典的xUnit风格的setup方法。 你可以根据需要混合使用两种样式,逐步从经典样式移动到新样式。 你也可以从现有的unittest.TestCase样式或基于nose的项目开始。

Fixture函数作为用例参数使用

测试用例可以通过在其参数中使用Fixture函数名称来接收Fixture函数。 每个fixture参数名称所对应的函数,可以通过使用@pytest.fixture注册成为一个fixture函数,来为测试用例提供一个Fixture函数。 让我们看一个只包含一个fixture和一个使用它的测试用例的简单独立测试模块:

Copy# ./test_smtpsimple.py内容import pytest@pytest.fixturedefsmtp_connection():import smtplibreturn smtplib.SMTP("smtp.gmail.com",587,timeout=5)deftest_ehlo(smtp_connection):response,msg = smtp_connection.ehlo()assert response == 250assert0# for demo purposes

这里,test_ehlo需要smtp_connection来提供Fixture函数。pytest将发现并调用带@pytest.fixture装饰器的smtp_connection fixture函数。 运行测试如下所示:

Copy$ pytest test_smtpsimple.py
=========================== test session starts ============================
platform linux -- Python 3.x.y,pytest-3.x.y,py-1.x.y,pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR,inifile:
collected 1 itemtest_smtpsimple.py F                                                 [100%]================================= FAILURES =================================
________________________________ test_ehlo _________________________________smtp_connection = <smtplib.SMTP object at 0xdeadbeef>def test_ehlo(smtp_connection):response,msg = smtp_connection.ehlo()assert response == 250
>       assert 0 # for demo purposes
E       assert 0test_smtpsimple.py:11: AssertionError
========================= 1 failed in 0.12 seconds =========================

在测试失败的回溯信息中,我们看到测试用例是使用smtp_connection参数调用的,即由fixture函数创建的smtplib.SMTP()实例。测试用例在我们故意的assert 0上失败。以下是pytest用这种方式调用测试用例使用的确切协议:

Fixture函数: 通过依赖注入实现

Fixture函数允许测试用例能轻松引入预先定义好的初始化准备函数,而无需关心导入/设置/清理方法的细节。 这是依赖注入的一个主要示例,其中fixture函数的函数扮演”注入器“的角色,测试用例来“消费”这些Fixture函数。

conftest.py: 共享Fixture函数

如果在测试中需要使用多个测试文件中的Fixture函数,则可以将其移动到conftest.py文件中,所需的Fixture函数会自动被Pytest发现,而不需要再每次导入。 fixture函数的发现顺序从测试类开始,然后是测试模块,然后是conftest.py文件,最后是内置和第三方插件。

你还可以使用conftest.py文件来实现本地每个目录的插件。

共享测试数据

如果要使用数据文件中的测试数据,最好的方法是将这些数据加载到fixture函数中以供测试用例注入使用。这利用到了pytest的自动缓存机制。

另一个好方法是在tests文件夹中添加数据文件。 还有社区插件可用于帮助处理这方面的测试,例如:pytest-datadir和pytest-datafiles。

生效范围:在测试类/测试模块/测试会话中共享Fixture函数

由于Fixture函数需要连接形成依赖网,而通常创建时间比较长。 扩展前面的示例,我们可以在@pytest.fixture调用中添加scope ="module"参数,以使每个测试模块只调用一次修饰的smtp_connection fixture函数(默认情况下,每个测试函数调用一次)。 因此,测试模块中的多个测试用例将各自注入相同的smtp_connectionFixture函数,从而节省时间。scope参数的可选值包括:function(函数),class(类),module(模块),package(包)及 session(会话)。

下一个示例将fixture函数放入单独的conftest.py文件中,以便来自目录中多个测试模块的测试可以访问fixture函数:

Copy# conftest.py文件内容import pytest
import smtplib@pytest.fixture(scope="module")defsmtp_connection():return smtplib.SMTP("smtp.gmail.com",587,timeout=5)

Fixture函数的名称依然是smtp_connection,你可以通过在任何测试用例或fixture函数(在conftest.py所在的目录中或下面)使用参数smtp_connection作为输入参数来访问其结果:

Copy# test_module.py文件内容deftest_ehlo(smtp_connection):response,msg = smtp_connection.ehlo()assert response == 250assertb"smtp.gmail.com"in msgassert0# for demo purposesdeftest_noop(smtp_connection):response,msg = smtp_connection.noop()assert response == 250assert0# for demo purposes

我们故意插入失败的assert 0语句,以便检查发生了什么,运行测试并查看结果:

Copy$ pytest test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y,pytest-4.x.y,py-1.x.y,pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR,inifile:
collected 2 itemstest_module.py FF                                                    [100%]================================= FAILURES =================================
________________________________ test_ehlo _________________________________smtp_connection = <smtplib.SMTP object at 0xdeadbeef>def test_ehlo(smtp_connection):response,msg = smtp_connection.ehlo()assert response == 250assert b"smtp.gmail.com"in msg
>       assert 0  # for demo purposes
E       assert 0test_module.py:6: AssertionError
________________________________ test_noop _________________________________smtp_connection = <smtplib.SMTP object at 0xdeadbeef>def test_noop(smtp_connection):response,msg = smtp_connection.noop()assert response == 250
>       assert 0  # for demo purposes
E       assert 0test_module.py:11: AssertionError
========================= 2 failed in 0.12 seconds =========================

你会看到两个assert 0失败信息,更重要的是你还可以看到相同的(模块范围的)smtp_connection对象被传递到两个测试用例中,因为pytest在回溯信息中显示传入的参数值。 因此,使用smtp_connection的两个测试用例运行速度与单个函数一样快,因为它们重用了相同的Fixture函数。

如果你决定要使用session(会话,一次运行算一次会话)范围的smtp_connection对象,则只需如下声明:

Copy@pytest.fixture(scope="session")defsmtp_connection():# the returned fixture value will be shared for# all tests needing it...

最后,class(类)范围将为每个测试类调用一次Fixture函数。

注意:
Pytest一次只会缓存一个Fixture函数。 这意味着当使用参数化fixture时,pytest可能会在给定范围内多次调用fixture函数。

package(包)范围的fixture(实验性函数)

3.7版本新函数

在pytest 3.7中,引入了包范围。 当包的最后一次测试结束时,最终确定包范围的fixture函数。

警告:
此函数是实验性的,如果在获得更多使用后发现隐藏的角落情况或此函数的严重问题,可能会在将来的版本中删除。

谨慎使用此新函数,请务必报告你发现的任何问题。

范围大的Fixture函数优先执行

3.5版本新函数

在测试函数的Fixture函数请求中,较大范围的Fixture(例如session会话级)较低范围的fixture(例如function函数级或class类级优先执行。相同范围的Fixture函数的按引入的顺序及Fixture函数之间的依赖关系按顺序调用。

请考虑以下代码:

Copy@pytest.fixture(scope="session")defs1():pass@pytest.fixture(scope="module")defm1():pass@pytest.fixturedeff1(tmpdir):pass@pytest.fixturedeff2():passdeftest_foo(f1,m1,f2,s1):...

test_foo中Fixture函数将按以下顺序执行:

  1. s1:是大高范围的Fixture(会话级)

  1. m1:是第二大的Fixture(模块级)

  1. tmpdir:是一个函数级的Fixture,f1依赖它,因此它需要在f1前调用

  1. f1:是test_foo参数列表中第一个函数范围的Fixture。

  1. f2:是test_foo参数列表中最后一个函数范围的Fixture。

Fixture结束/执行teardown代码

当fixture超出范围时,通过使用yield语句而不是return,pytest支持fixture执行特定的teardown代码。yield语句之后的所有代码都视为teardown代码:

Copy# conftest.py文件内容import smtplib
import pytest@pytest.fixture(scope="module")defsmtp_connection():smtp_connection = smtplib.SMTP("smtp.gmail.com",587,timeout=5)yield smtp_connection  # provide the fixture valueprint("teardown smtp")smtp_connection.close()

无论测试的异常状态如何,print和smtp.close()语句将在模块中的最后一个测试完成执行时执行。

让我们执行一下(上文的test_module.py):

Copy$ pytest -s -q --tb=no
FFteardown smtp2 failed in 0.12 seconds

我们看到smtp_connection实例在两个测试完成执行后完成。 请注意,如果我们使用scope ='function'修饰我们的fixture函数,那么每次单个测试都会进行fixture的setup和teardown。 在任何一种情况下,测试模块本身都不需要改变或了解fixture函数的这些细节。

请注意,我们还可以使用with语句无缝地使用yield语法:

Copy# test_yield2.py文件内容import smtplib
import pytest@pytest.fixture(scope="module")defsmtp_connection():with smtplib.SMTP("smtp.gmail.com",587,timeout=5) as smtp_connection:yield smtp_connection  # provide the fixture value

测试结束后,smtp_connection连接将关闭,因为当with语句结束时,smtp_connection对象会自动关闭。

请注意,如果在设置代码期间(yield关键字之前)发生异常,则不会调用teardown代码(在yield之后)。

执行teardown代码的另一种选择是利用请求上下文对象的addfinalizer方法来注册teardown函数。

以下是smtp_connectionfixture函数更改为使用addfinalizer进行teardown:

Copy# content of conftest.pyimport smtplib
import pytest@pytest.fixture(scope="module")defsmtp_connection(request):smtp_connection = smtplib.SMTP("smtp.gmail.com",587,timeout=5)deffin():print("teardown smtp_connection")smtp_connection.close()request.addfinalizer(fin)return smtp_connection  # provide the fixture value

yield和addfinalizer方法在测试结束后调用它们的代码时的工作方式类似,但addfinalizer相比yield有两个主要区别:

  1. 使用addfinalizer可以注册多个teardown函数。

  1. 无论fixture中setup代码是否引发异常,都将始终调用teardown代码。 即使其中一个资源无法创建/获取,也可以正确关闭fixture函数创建的所有资源:

Copy@pytest.fixturedefequipments(request):r = []for port in ('C1','C3','C28'):equip = connect(port)request.addfinalizer(equip.disconnect)r.append(equip)return r

在上面的示例中,如果“C28”因异常而失败,则“C1”和“C3”仍将正确关闭。 当然,如果在注册finalize函数之前发生异常,那么它将不会被执行。

Fixture函数中使用测试用例相关信息

Fixture函数可以接受request对象来内省“请求”测试函数,类或模块上下文。进一步扩展前一个smtp_connectionfixture例子,让我们从使用我们的fixture的测试模块中读取一个可选的服务器URL:

Copy# content of conftest.pyimport pytest
import smtplib@pytest.fixture(scope="module")defsmtp_connection(request):server = getattr(request.module,"smtpserver","smtp.gmail.com")smtp_connection = smtplib.SMTP(server,587,timeout=5)yield smtp_connectionprint("finalizing %s (%s)" % (smtp_connection,server))smtp_connection.close()

我们使用该request.module属性可选地smtpserver从测试模块获取 属性。如果我们再次执行,那么没有太大变化:

Copy$ pytest -s -q --tb=no
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)2 failed in 0.12 seconds

让我们快速创建另一个测试模块,该模块实际上在其模块命名空间中设置服务器URL:

Copy# content of test_anothersmtp.pysmtpserver = "mail.python.org"# will be read by smtp fixturedeftest_showhelo(smtp_connection):assert0,smtp_connection.helo()

运行它:

Copy$ pytest -qq --tb=short test_anothersmtp.py
F                                                                    [100%]
================================= FAILURES =================================
______________________________ test_showhelo _______________________________
test_anothersmtp.py:5: in test_showheloassert 0,smtp_connection.helo()
E   AssertionError: (250,b'mail.python.org')
E   assert 0
------------------------- Captured stdout teardown -------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef> (mail.python.org)

瞧!该smtp_connectionFixture函数函数从模块命名空间拿起我们的邮件服务器名称。

Fixture函数返回函数对象

译者注:Fixture返回一个函数,以支持根据参数得到不同的结果

“Fixture作为函数工厂”模式,可以支持在用例中根据不同的参数使用Fixture得到不同的结果。Fixture可以返回一个内部定义的函数,而不是直接返回数据,便可以在用例中使用该函数通过不同的参数获取到不同的结果。

Fixture函数工厂方法可根据需要提供参数生成Fixture和方法:

Copy@pytest.fixturedefmake_customer_record():def_make_customer_record(name):return {"name": name,"orders": []}return _make_customer_recorddeftest_customer_records(make_customer_record):customer_1 = make_customer_record("Lisa")customer_2 = make_customer_record("Mike")customer_3 = make_customer_record("Meredith")

如果工厂创建的数据需要管理,那么Fixture函数可以处理:

Copy@pytest.fixturedefmake_customer_record():created_records = []def_make_customer_record(name):record = models.Customer(name=name,orders=[])created_records.append(record)return recordyield _make_customer_recordfor record in created_records:record.destroy()deftest_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_connectionFixture函数实例,这将导致使用Fixture函数的所有测试运行两次。fixture函数通过特殊request对象访问每个参数:

Copy# content of conftest.pyimport pytest
import smtplib@pytest.fixture(scope="module",params=["smtp.gmail.com","mail.python.org"])defsmtp_connection(request):smtp_connection = smtplib.SMTP(request.param,587,timeout=5)yield smtp_connectionprint("finalizing %s" % smtp_connection)smtp_connection.close()

主要的变化是paramswith 的声明@pytest.fixture,一个值列表,每个值的Fixture函数函数将执行,并可以通过访问值request.param。没有测试函数代码需要更改。那么让我们再做一次:

Copy$ pytest -q test_module.py
FFFF                                                                 [100%]
================================= FAILURES =================================
________________________ test_ehlo[smtp.gmail.com] _________________________smtp_connection = <smtplib.SMTP object at 0xdeadbeef>def test_ehlo(smtp_connection):response,msg = smtp_connection.ehlo()assert response == 250assert b"smtp.gmail.com"in msg
>       assert 0  # for demo purposes
E       assert 0test_module.py:6: AssertionError
________________________ test_noop[smtp.gmail.com] _________________________smtp_connection = <smtplib.SMTP object at 0xdeadbeef>def test_noop(smtp_connection):response,msg = smtp_connection.noop()assert response == 250
>       assert 0  # for demo purposes
E       assert 0test_module.py:11: AssertionError
________________________ test_ehlo[mail.python.org] ________________________smtp_connection = <smtplib.SMTP object at 0xdeadbeef>def test_ehlo(smtp_connection):response,msg = smtp_connection.ehlo()assert response == 250
>       assert b"smtp.gmail.com"in msg
E       AssertionError: assert b'smtp.gmail.com'in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8\nCHUNKING'test_module.py:5: AssertionError
-------------------------- Captured stdout setup ---------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef>
________________________ test_noop[mail.python.org] ________________________smtp_connection = <smtplib.SMTP object at 0xdeadbeef>def test_noop(smtp_connection):response,msg = smtp_connection.noop()assert response == 250
>       assert 0  # for demo purposes
E       assert 0test_module.py:11: AssertionError
------------------------- Captured stdout teardown -------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef>
4 failed in 0.12 seconds

我们看到我们的两个测试函数分别针对不同的smtp_connection实例运行了两次 。另请注意,对于mail.python.org 连接,第二个测试失败,test_ehlo因为预期的服务器字符串不同于发送到服务器字符串。

Pytest将建立一个字符串,它是用于在参数化Fixture函数,例如每个器材值测试ID test_ehlo[smtp.gmail.com]和 test_ehlo[mail.python.org]在上述实施例。这些ID可用于-k选择要运行的特定案例,并且还可以在失败时识别特定案例。运行pytest --collect-only将显示生成的ID。

数字,字符串,布尔值和None将在测试ID中使用它们通常的字符串表示形式。对于其他对象,Pytest将根据参数名称生成一个字符串。可以使用ids关键字参数自定义测试ID中用于特定Fixture函数值的字符串 :

Copy# content of test_ids.pyimport pytest@pytest.fixture(params=[0,1],ids=["spam","ham"])defa(request):return request.paramdeftest_a(a):passdefidfn(fixture_value):if fixture_value == 0:return"eggs"else:returnNone@pytest.fixture(params=[0,1],ids=idfn)defb(request):return request.paramdeftest_b(b):pass

上面显示了如何ids使用要使用的字符串列表或将使用fixture值调用的函数,然后必须返回要使用的字符串。在后一种情况下,如果函数返回,None则将使用pytest的自动生成的ID。

运行上述测试会导致使用以下测试ID:

Copy$ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y,pytest-5.x.y,py-1.x.y,pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 10 items
<Module test_anothersmtp.py><Function test_showhelo[smtp.gmail.com]><Function test_showhelo[mail.python.org]>
<Module test_ids.py><Function test_a[spam]><Function test_a[ham]><Function test_b[eggs]><Function test_b[1]>
<Module test_module.py><Function test_ehlo[smtp.gmail.com]><Function test_noop[smtp.gmail.com]><Function test_ehlo[mail.python.org]><Function test_noop[mail.python.org]>======================= no tests ran in0.12 seconds =======================

使用参数化Fixture函数标记

pytest.param()可用于在参数化Fixture函数的值集中应用标记,其方式与@pytest.mark.parametrize一样。

例如:

Copy# test_fixture_marks.py文件内容import pytest
@pytest.fixture(params=[0,1,pytest.param(2,marks=pytest.mark.skip)])defdata_set(request):return request.paramdeftest_data(data_set):pass

运行此测试将跳过data_set带值的调用2:

Copy$ pytest test_fixture_marks.py -v
=========================== test session starts ============================
platform linux -- Python 3.x.y,pytest-5.x.y,py-1.x.y,pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collecting ... collected 3 itemstest_fixture_marks.py::test_data[0] PASSED                           [ 33%]
test_fixture_marks.py::test_data[1] PASSED                           [ 66%]
test_fixture_marks.py::test_data[2] SKIPPED                          [100%]=================== 2 passed,1 skipped in 0.12 seconds ====================

模块化:在Fixture函数中使用其他Fixture函数

你不仅可以在测试函数中使用Fixture函数,而且Fixture函数函数可以自己使用其他Fixture函数。这有助于你的Fixture函数的模块化设计,并允许在许多项目中重复使用特定于框架的Fixture函数。作为一个简单的例子,我们可以扩展前面的例子并实例化一个对象app,我们将已经定义的smtp_connection资源粘贴 到它中:

Copy# content of test_appsetup.pyimport pytestclassApp(object):def__init__(self,smtp_connection):self.smtp_connection = smtp_connection@pytest.fixture(scope="module")defapp(smtp_connection):return App(smtp_connection)deftest_smtp_connection_exists(app):assert app.smtp_connection

这里我们声明一个appfixture,它接收先前定义的 smtp_connectionfixture并App用它实例化一个对象。我们来吧:

Copy$ pytest -v test_appsetup.py
=========================== test session starts ============================
platform linux -- Python 3.x.y,pytest-5.x.y,py-1.x.y,pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collecting ... collected 2 itemstest_appsetup.py::test_smtp_connection_exists[smtp.gmail.com] PASSED [ 50%]
test_appsetup.py::test_smtp_connection_exists[mail.python.org] PASSED [100%]========================= 2 passed in 0.12 seconds =========================

由于参数化smtp_connection,测试将使用两个不同的App实例和相应的smtp服务器运行两次。没有必要为appFixture函数要意识到的smtp_connection 参数化,因为pytest将全面分析Fixture函数依赖关系图。

请注意,appFixture函数具有范围module并使用模块范围的smtp_connectionFixture函数。如果smtp_connection缓存在session范围上,该示例仍然可以工作 :Fixture函数使用“更广泛”的范围Fixture函数,但不是相反的方式:会话范围的Fixture函数不能以有意义的方式使用模块范围的Fixture函数。

使用Fixture函数自动组织测试用例

Pytest在测试运行期间最小化活动Fixture函数的数量。如果你有一个参数化Fixture函数,那么使用它的所有测试将首先用一个实例执行,然后在创建下一个Fixture函数实例之前调用终结器。除此之外,这还可以简化对创建和使用全局状态的应用程序的测试。

以下示例使用两个参数化Fixture函数,其中一个基于每个模块作用域,并且所有函数执行print调用以显示设置/拆卸流程:

Copy# content of test_module.pyimport pytest@pytest.fixture(scope="module",params=["mod1","mod2"])defmodarg(request):param = request.paramprint("  SETUP modarg %s" % param)yield paramprint("  TEARDOWN modarg %s" % param)@pytest.fixture(scope="function",params=[1,2])defotherarg(request):param = request.paramprint("  SETUP otherarg %s" % param)yield paramprint("  TEARDOWN otherarg %s" % param)deftest_0(otherarg):print("  RUN test0 with otherarg %s" % otherarg)
deftest_1(modarg):print("  RUN test1 with modarg %s" % modarg)
deftest_2(otherarg,modarg):print("  RUN test2 with otherarg %s and modarg %s" % (otherarg,modarg))

让我们以详细模式运行测试并查看打印输出:

Copy$ pytest -v -s test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y,pytest-5.x.y,py-1.x.y,pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collecting ... collected 8 itemstest_module.py::test_0[1]   SETUP otherarg 1RUN test0 with otherarg 1
PASSED  TEARDOWN otherarg 1test_module.py::test_0[2]   SETUP otherarg 2RUN test0 with otherarg 2
PASSED  TEARDOWN otherarg 2test_module.py::test_1[mod1]   SETUP modarg mod1RUN test1 with modarg mod1
PASSED
test_module.py::test_2[mod1-1]   SETUP otherarg 1RUN test2 with otherarg 1 and modarg mod1
PASSED  TEARDOWN otherarg 1test_module.py::test_2[mod1-2]   SETUP otherarg 2RUN test2 with otherarg 2 and modarg mod1
PASSED  TEARDOWN otherarg 2test_module.py::test_1[mod2]   TEARDOWN modarg mod1SETUP modarg mod2RUN test1 with modarg mod2
PASSED
test_module.py::test_2[mod2-1]   SETUP otherarg 1RUN test2 with otherarg 1 and modarg mod2
PASSED  TEARDOWN otherarg 1test_module.py::test_2[mod2-2]   SETUP otherarg 2RUN test2 with otherarg 2 and modarg mod2
PASSED  TEARDOWN otherarg 2TEARDOWN modarg mod2========================= 8 passed in 0.12 seconds =========================

你可以看到参数化模块范围的modarg资源导致测试执行的排序,从而导致尽可能少的“活动”资源。mod1参数化资源的终结器在mod2资源建立之前执行 。

特别注意test_0是完全独立的并且首先完成。然后执行mod1test_1 mod1,然后执行test_2 ,然后执行test_1,mod2最后执行test_2 mod2。

该otherarg参数化资源(其函数范围)是之前设置和使用它的每一个测试后撕开了下来。

在类/模块/项目中使用Fixture函数

有时,测试函数不需要直接访问Fixture函数对象。例如,测试可能需要使用空目录作为当前工作目录,但不关心具体目录。以下是如何使用标准tempfile和pytest fixture来实现它。我们将fixture的创建分成conftest.py文件:

Copy# content of conftest.pyimport pytest
import tempfile
import os@pytest.fixture()defcleandir():newpath = tempfile.mkdtemp()os.chdir(newpath)

并通过useFixture函数标记在测试模块中声明它的使用方法:

Copy# content of test_setenv.pyimport os
import pytest@pytest.mark.useFixture函数("cleandir")classTestDirectoryInit(object):deftest_cwd_starts_empty(self):assert os.listdir(os.getcwd()) == []withopen("myfile","w") as f:f.write("hello")deftest_cwd_again_starts_empty(self):assert os.listdir(os.getcwd()) == []

由于useFixture函数标记,cleandir每个测试用例的执行都需要Fixture函数,就像为每个测试用例指定一个“cleandir”函数参数一样。让我们运行它来验证我们的Fixture函数是否已激活且测试通过:

Copy$ pytest -q
..                                                                  [100%]
2 passed in 0.12 seconds

你可以像这样指定多个Fixture函数:

Copy@pytest.mark.useFixture函数("cleandir","anotherfixture")deftest():...

你可以使用标记机制的通用函数在测试模块级别指定Fixture函数使用情况:

Copypytestmark = pytest.mark.useFixture函数("cleandir")

请注意,必须调用指定的变量pytestmark,分配例如 foomark不会激活Fixture函数。

也可以将项目中所有测试所需的Fixture函数放入ini文件中:

Copy content of pytest.ini
[pytest]
useFixture函数 = cleandir

警告
请注意,此标记对Fixture函数函数没有影响。例如,这将无法按预期工作:

@pytest.mark.useFixture函数("my_other_fixture")

@pytest.fixture

def my_fixture_that_sadly_wont_use_my_other_fixture():

...

目前,这不会产生任何错误或警告,但这应由#3664处理。

自动使用Fixture函数(xUnit 框架的setup固定方法)

有时,你可能希望自动调用fixture,而无需显式声明函数参数或使用useFixture函数装饰器。作为一个实际的例子,假设我们有一个数据库fixture,它有一个开始/回滚/提交架构,我们希望通过事务和回滚自动包围每个测试用例。以下是这个想法的虚拟自包含实现:

Copy# content of test_db_transact.pyimport pytestclassDB(object):def__init__(self):self.intransaction = []defbegin(self,name):self.intransaction.append(name)defrollback(self):self.intransaction.pop()@pytest.fixture(scope="module")defdb():return DB()classTestClass(object):@pytest.fixture(autouse=True)deftransact(self,request,db):db.begin(request.function.__name__)yielddb.rollback()deftest_method1(self,db):assert db.intransaction == ["test_method1"]deftest_method2(self,db):assert db.intransaction == ["test_method2"]

类级别的transactfixture用autouse = true标记, 这意味着类中的所有测试用例都将使用此fixture而无需在测试函数签名中或使用类级useFixture函数装饰器进行陈述。

如果我们运行它,我们得到两个通过测试:

Copy$ pytest -q
..                                                                  [100%]
2 passed in 0.12 seconds

以下是autouseFixture函数在其他范围内的工作原理:

  • autouse Fixture函数服从scope=关键字参数:如果autouse fixture具有scope='session'它,它将只运行一次,无论它在何处定义。scope='class'意味着它将每班运行一次,等等。

  • 如果在测试模块中定义了autouse fixture,则其所有测试函数都会自动使用它。

  • 如果在conftest.py文件中定义了autouse fixture,那么其目录下所有测试模块中的所有测试都将调用fixture。

  • 最后,请谨慎使用:如果你在插件中定义了autouse fixture,则会在安装插件的所有项目中为所有测试调用它。如果Fixture函数仅在任何情况下在某些设置(例如ini文件中)的情况下工作,则这可能是有用的。这样的全局Fixture函数应该总是快速确定它是否应该做任何工作并避免昂贵的进口或计算。

请注意,上述transactFixture函数很可能是你希望在项目中可用的Fixture函数,而不是通常处于活动状态。规范的方法是将transact定义放入conftest.py文件中,而不使用autouse:

Copy# content of conftest.py@pytest.fixturedeftransact(request,db):db.begin()yielddb.rollback()
然后让一个TestClass通过声明需要使用它:@pytest.mark.useFixture函数("transact")classTestClass(object):deftest_method1(self):...

此TestClass中的所有测试用例都将使用事务Fixture函数,而模块中的其他测试类或函数将不使用它,除非它们还添加transact引用。

不同级别的Fixture函数的优先级

相对于在较大范围的测试套件中的Test Fixture函数方法,在较小范围子套件你可能需要重写和覆盖外层的Test Fixture函数方法,从而保持测试代码的可读性和可维护性。

在文件目录级别(通过conftest文件)重写Fixture函数

假设用例目录结构为:

Copytests/__init__.pyconftest.py# content of tests/conftest.pyimport pytest@pytest.fixturedefusername():return'username'test_something.py# content of tests/test_something.pydeftest_username(username):assert username == 'username'subfolder/__init__.pyconftest.py# content of tests/subfolder/conftest.pyimport pytest@pytest.fixturedefusername(username):return'overridden-' + usernametest_something.py# content of tests/subfolder/test_something.pydeftest_username(username):assert username == 'overridden-username'

你可以看到,基础/上级Fixture函数方法可以通过子文件夹下的conftest.py中同名的Fixture函数方法覆盖,非常简单,只需要按照上面的例子使用即可.

在测试模块级别重写Fixture函数方法

假设用例文件结构如下:

Copytests/__init__.pyconftest.py# content of tests/conftest.py        @pytest.fixturedefusername():return'username'test_something.py# content of tests/test_something.pyimport pytest@pytest.fixturedefusername(username):return'overridden-' + usernamedeftest_username(username):assert username == 'overridden-username'test_something_else.py# content of tests/test_something_else.pyimport pytest@pytest.fixturedefusername(username):return'overridden-else-' + usernamedeftest_username(username):assert username == 'overridden-else-username'

上面的例子中,用例模块(文件)中的Fixture函数会覆盖文件夹conftest.py中同名的Fixture函数方法

在直接参数化方法中覆盖Fixture函数方法

假设用例文件结构为:

Copytests/__init__.pyconftest.py# content of tests/conftest.pyimport pytest@pytest.fixturedefusername():return'username'        @pytest.fixturedefother_username(username):return'other-' + usernametest_something.py# content of tests/test_something.pyimport pytest@pytest.mark.parametrize('username',['directly-overridden-username'])deftest_username(username):assert username == 'directly-overridden-username'        @pytest.mark.parametrize('username',['directly-overridden-username-other'])deftest_username_other(other_username):assert other_username == 'other-directly-overridden-username-other'

在上面的示例中,username Fixture函数的结果值被参数化值覆盖。 请注意,即使测试不直接使用(也未在函数原型中提及),也可以通过这种方式覆盖fixture的值。

使用非参数化Fixture函数覆盖参数化Fixture函数方法,反之亦然

假设用例结构为:

Copytests/__init__.pyconftest.py# content of tests/conftest.pyimport pytest@pytest.fixture(params=['one','two','three'])defparametrized_username(request):return request.param@pytest.fixturedefnon_parametrized_username(request):return'username'test_something.py# content of tests/test_something.pyimport pytest@pytest.fixturedefparametrized_username():return'overridden-username'        @pytest.fixture(params=['one','two','three'])defnon_parametrized_username(request):return request.paramdeftest_username(parametrized_username):assert parametrized_username == 'overridden-username'deftest_parametrized_username(non_parametrized_username):assert non_parametrized_username in ['one','two','three']test_something_else.py# content of tests/test_something_else.pydeftest_username(parametrized_username):assert parametrized_username in ['one','two','three']deftest_username(non_parametrized_username):assert non_parametrized_username == 'username'

在上面的示例中,使用非参数化Fixture函数覆盖参数化Fixture函数,以及使用参数化Fixture覆盖非参数化Fixture以用于特定测试模块。 这同样适用于文件夹级别的Fixture函数

实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

电商项目实战

web测试项目

web+App+h5+小程序 测试项目

接口自动化测试实战项目

Linux实战项目

面试资料

我们进阶学习自动化测试必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

以上资料,对于想要测试进阶的朋友们来说应该会很有帮助,需要的小伙伴可以后台私信找我免费领取

总结

我见过很多leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了好几年,更夸张的是7、8年工作内容的重复性比较高,没有什么技术含量的工作。

凡事要趁早,特别是技术行业,一定要提升技术功底,丰富自动化项目实战经验,这对于你未来几年职业规划,以及测试技术掌握的深度非常有帮助。

如有不懂还要咨询下方小卡片,博主也希望和志同道合的测试人员一起学习进步

在适当的年龄,选择适当的岗位,尽量去发挥好自己的优势。

我的自动化测试开发之路,一路走来都离不每个阶段的计划,因为自己喜欢规划和总结,

测试开发视频教程、学习笔记领取传送门!!!

Pytest自动化框架-权威教程05-Pytest fixtures:清晰 模块化 易扩展相关推荐

  1. Python + Pytest 自动化框架的用例依赖实操

    今日分享主题:Python + Pytest 自动化框架中测试用例依赖的探索与实践,今天分享的内容以"依赖别名+scope='package'"进行演示. 1.安装 pytest- ...

  2. 手把手教你从0到1搭建web ui自动化框架(python3+selenium3+pytest)

    -前期准备 -环境 -实战: 从0开始 前期准备 为更好的学习自动化框架搭建,你需要提前了解以下知识: python基础知识 pytest单元测试框架 PO模式 selenium使用 环境 本次我们自 ...

  3. python自动化框架测试实操_自动化框架之 python+selenium+pytest

    1.概述 selenium: 基于JavaScript代码库的自动化测试框架,通过脚本语言,模拟用户行为操作,最接近用户真实场景,实现对web自动测试. Selenium,是目前的最火爆企业最主流的w ...

  4. 第三代api自动化测试框架使用教程(pytest+allure+sql+yaml)

    使用教程 一.配置 1.环境配置 2.框架配置 3.启动入口 二.用例编写 1.用例模板 2.参数依赖写法 2.函数(方法插件)写法 3.接口上传文件和表单参数 4.接口上传json参数 5.接口无数 ...

  5. pytest+yaml框架环境配置和使用教程

    pytest+yaml框架使用教程 一.框架功能介绍 二.框架启动 1.源码下载,项目打开 1.1.[框架源码](https://gitee.com/jianglisha/pytest_api_yam ...

  6. 最全最新pytest接口自动化框架

    pytest接口自动化框架介绍: Python+pytest+allure+log+yaml(默认数据和动态传参)+数据驱动+关键字驱动+pymysql(数据清理尽量接口清理)+一键切换环境+多角色随 ...

  7. 一、Pytest自动化测试框架 — Pytest和unittest测试框架原理

    目录 前言 ###文章内容有配套的学习视频和笔记都放在了文章末尾### 1.什么是单元测试框架 2.单元测试框架主要做什么 3.单元测试框架和自动化测试框架有什么关系 4.Pytest测试框架说明 5 ...

  8. Pytest 测试框架——数据驱动

    引言 前面已经和大家介绍过 Unittest 测试框架的数据驱动框架 DDT,以及其实现原理.今天和大家分享的是 Pytest 测试框架的数据驱动,Pytest 测试框架的数据驱动是由 pytest ...

  9. pytest+yaml设计接口自动化框架过程记录(一步一步记录如何设计,完结撒花),源码提供,视频教程

    pytest+yaml设计接口自动化框架过程记录 第三代框架使用教程,该框架比现在这个完善了很多 框架简介 框架运行演示和功能介绍视频 pytest+yaml框架环境配置和使用教程 0.去年也写了一个 ...

最新文章

  1. 【翻译】将Ext JS Grid转换为Excel表格
  2. http传输html图片方式,http怎么样传输图片?二进制流还是base64编码
  3. linux修改selinux
  4. 选择Vert.x的3个理由
  5. c语言 amp a 1,c语言那些细节之a+1和a+1的区别
  6. 139. 单词拆分(JavaScript)
  7. stm32数据手册boot_STM32问题集之BOOT0和BOOT1的作用
  8. 计算机视觉实战(十一)Scale Invariant Feature Transform(SIFT)(附完整代码)
  9. vscode搭建汇编环境
  10. 压力换算公斤单位换算_压力单位换算方法
  11. cad计算机配置要求,CAD对电脑硬件的配置要求
  12. docker ps3netsrv_QNAP 威联通 NAS TS-212P应用系列 篇一:实践基于QNAP平台搭建PS3NETSRV服务...
  13. contextcapture多区块点云_Smart 3D (ContextCapture) 4.4.6版本最新功能
  14. vue 登录 动态树 表格 cud
  15. ⅰsee是什么意思_l see 什么意思
  16. java中isolate时间_Flutter 92: 图解 Dart 单线程实现异步处理之 Isolate (一)
  17. 要脱大家一起脱:网络实名制与官员财产透明化
  18. 【NLP】Python NLTK 走进大秦帝国
  19. 电信运营商云计算战略和发展现状
  20. 从ELK到EFK,日志系统的高级玩法儿

热门文章

  1. Temporal框架学习
  2. @Temporal()注解的使用
  3. EF 三层 BLL DAL
  4. echart 乡镇地图 地图数据在线生成工具_自定义样式生成工具
  5. sqlmap图形化_sqlmap详解
  6. 1414ajax,jsp1414新闻发布网站sqlserver
  7. 零基础在UE4中实现物体按照轨迹行驶
  8. Tushare 注册、更新与数据获取
  9. 没时间做副项目?这个程序员是如何做到的
  10. 元宇宙是什么:对社会有什么影响