前后置处理(固件/夹具)
测试用例:
前置操作(准备工作-准备环境、数据、资源等)
后置操作(清理工作-关闭环境、清理数据、释放资源等)
pytest
常用的前后置处理有三种:setup/teardown
、fixture
、conftest.py
结合fixture
。下面进行逐一介绍。
setup/teardown
pytest推荐的前后置处理是后面两种方式,setup/teardown
方式已很少使用,所以这里仅做了解。
pytest的setup/teardown
跟unittest类似,主要有3种(不止3种):
- 模块级别:setup_module,teardown_module (对整个py文件起作用)
- 类级别:setup_class,teardown_class (对单个测试类起作用)
- 函数/方法级别:setup,teardown (对测试类中的测试方法起作用)
import pytest
def setup_module():
print("\n-- setup_module --")
def teardown_module():
print("\n-- steardown_module --")
class TestDemo10:
@classmethod
def setup_class(cls):
print("\n-- setup_class --")
@classmethod
def teardown_class(cls):
print("\n-- teardown_class --")
def setup(self):
print("\n-- setup --执行测试用例之前初始化代码:测试环境准备,准备测试数据")
def teardown(self):
print("\n-- teardown --在执行测试用例之后的扫尾工作:清理测试数据,恢复测试环境")
def test_case1(self):
x = 1
assert x
def test_case2(self):
x = 0
assert not x
def test_case3(self):
x = 'python'
assert 'o' in x
class TestDemo11:
def test_case4(self):
x = 'python'
assert 'i' not in x
if __name__ == '__main__':
pytest.main()
执行测试:
$ pytest testcases/test_10_demo.py
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 4 items
testcases/test_10_demo.py::TestDemo10::test_case1
-- setup_module --
-- setup_class --
-- setup --执行测试用例之前初始化代码:测试环境准备,准备测试数据
PASSED
-- teardown --在执行测试用例之后的扫尾工作:清理测试数据,恢复测试环境
testcases/test_10_demo.py::TestDemo10::test_case2
-- setup --执行测试用例之前初始化代码:测试环境准备,准备测试数据
PASSED
-- teardown --在执行测试用例之后的扫尾工作:清理测试数据,恢复测试环境
testcases/test_10_demo.py::TestDemo10::test_case3
-- setup --执行测试用例之前初始化代码:测试环境准备,准备测试数据
PASSED
-- teardown --在执行测试用例之后的扫尾工作:清理测试数据,恢复测试环境
-- teardown_class --
testcases/test_10_demo.py::TestDemo11::test_case4 PASSED
-- steardown_module --
------------------------------------------- generated html file: file:///Users/laobai/workspaces/testdemo01/report.html -------------------------------------------
======================================================================== 4 passed in 0.03s ========================================================================
根据测试结果可以看出,setup/teardown
对当前测试类中的每个测试方法都有效,会在每个方法的前和后执行,setup_class/teardown_class
仅对当前测试类有效,在当前测试类的前和后仅执行一次,setup_module/teardown_module
对当前py文件的所有测试类都有效,在当前模块前和后仅执行一次。
如果只想对部分测试用例进行初始化,用setup/teardown
就无法实现了
fixture装饰器
fixture是pytest用于将测试前后进行准备、清理工作的代码分离出核心测试逻辑代码的一种机制。可以灵活的处理很多场景下的用例测试需求。
- 定义fixture跟定义普通函数一样,唯一的区别就是在函数上加个装饰器
@pytest.fixture()
,fixture命名不要用test_
开头(或在pytest.ini
中自定义的开头关键字),要跟测试用例区分开。 - fixture装饰器里的
scope
参数有5个可选值,表示不同的测试用例应用范围:function
、class
、module
、package
、seesion
。没有设置scope
的值,默认为function
- fixture可以有返回值,返回一个元组、列表或字典。如果没有return,会返回None;用例调用fixture的返回值,会直接把fixture的函数名称作为参数传入。
- 不同的fixture之间可以相互调用
基础语法
pytest使用@pytest.fixture()
装饰器来实现部分用例/全部用例的前后置,完整的语法格式如下:
fixture(scope='function' ,parms=None, autouse=False, ids=None, name=None)
参数说明:
scope
参数:标记方法的作用域。有5个可选值:function
(默认值,函数)、class
(类)、module
(模块)、package
(包)、session
(会话)function
:每一个函数或方法都会调用class
:每一个类调用一次,一个类中可以有多个方法module
:每一个.py文件调用一次,该文件内有可以有多个class和functionpackage
:是多个文件(整个项目)调用一次,可以跨.py文件调用session
:是当前运行的会话调用一次
params
:一个可选的参数列表,实现参数化autouse
:默认False,需要通过调用来激活fixture;如果为True,则所有符合条件的用例自动调用fixtureids
:用例标识ID,每个ids
对应于params
,如果没有ids
他们将从params
自动生成name
:fixture的重命名。若使用了name
,在使用时将传入name
,原来的函数名将不再生效
fixture是将setup/teardown
整合在一个fixture()
函数中实现,用yield
关键字区隔,yield
之前是setup
,yield
之后是teardown
。
@pytest.fixture(scope='function')
def login():
print("登录系统")
yield
print("退出登录")
前后置方法本质上是一个生成器函数,生产器函数在使用next
进行迭代时,执行到yeild
会返回数据,暂停执行,等待下一次进行迭代时才会继续执行,pytest就是利用的生成器的机制,通过yeild
将测试前后置代码分开执行。
为函数增加夹具
创建一个包:test_fixture,在包中创建test_demo01.py文件,这里
scope
定义为function
import pytest @pytest.fixture(scope='function') def login(): print("登录系统") yield print("退出登录") class TestCase1: def test_01(self): print("测试用例1") def test_02(self): print("测试用例2") if __name__ == '__main__': pytest.main()
执行测试:
$ pytest test_01_demo.py ======================================================================= test session starts ======================================================================= platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'} rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4 collected 2 items test_01_demo.py::TestCase1::test_01 测试用例1 PASSED test_01_demo.py::TestCase1::test_02 测试用例2 PASSED ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html ------------------------------------- ======================================================================== 2 passed in 0.03s ========================================================================
执行结果中,并未显示出夹具的登录和退出。这是因为上面的语法提到,
autouse
默认是False
。调用激活:在需要调用的函数或类将
fixture()
装饰器修饰的函数作为参数进行调用,代码如下:import pytest @pytest.fixture(scope='function') def login(): print("登录系统") yield print("退出登录") class TestCase1: def test_01(self): print("测试用例1") def test_02(self,login): print("测试用例2") if __name__ == '__main__': pytest.main()
执行测试:
$ pytest test_01_demo.py ======================================================================= test session starts ======================================================================= platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'} rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4 collected 2 items test_01_demo.py::TestCase1::test_01 测试用例1 PASSED test_01_demo.py::TestCase1::test_02 登录系统 测试用例2 PASSED退出登录 ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html ------------------------------------- ======================================================================== 2 passed in 0.03s ========================================================================
通过执行结果,可以看到,调用了
login
的函数增加了前后置的代码执行。这也说明了fixture
的灵活性,可以在一批用例中的某些用例设置前后置,而不是像setup/teardown
(包括unittest
)要么全部是,要么全部否。比如这里的例子,前置是登录,后置是退出登录,用例1可以是非登录状态下的测试,而用例2是登录状态下的测试。不通过单个用例激活,设置
autouse
为True
import pytest @pytest.fixture(scope='function', autouse=True) def login(): print("登录系统") yield print("退出登录") class TestCase1: def test_01(self): print("测试用例1") def test_02(self): print("测试用例2") if __name__ == '__main__': pytest.main()
执行测试:
$ pytest test_01_demo.py ======================================================================= test session starts ======================================================================= platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'} rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4 collected 2 items test_01_demo.py::TestCase1::test_01 登录系统 测试用例1 PASSED退出登录 test_01_demo.py::TestCase1::test_02 登录系统 测试用例2 PASSED退出登录 ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html ------------------------------------- ======================================================================== 2 passed in 0.02s ========================================================================
从测试结果可以看到两个用例都执行了前后置代码。
装饰器
@pytest.fixture()
不设置scope
的值,默认按scope='function'
执行import pytest @pytest.fixture(autouse=True) def login(): print("登录系统") yield print("退出登录") class TestCase4: def test_07(self): print("测试用例7") def test_08(self): print("测试用例8") class TestCase5: def test_09(self): print("测试用例9") def test_10(self): print("测试用例10") def test_11(): print("测试用例11") if __name__ == '__main__': pytest.main()
执行测试
$ pytest test_04_demo.py ======================================================================= test session starts ======================================================================= platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'} rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4 collected 5 items test_04_demo.py::TestCase4::test_07 登录系统 测试用例7 PASSED退出登录 test_04_demo.py::TestCase4::test_08 登录系统 测试用例8 PASSED退出登录 test_04_demo.py::TestCase5::test_09 登录系统 测试用例9 PASSED退出登录 test_04_demo.py::TestCase5::test_10 登录系统 测试用例10 PASSED退出登录 test_04_demo.py::test_11 登录系统 测试用例11 PASSED退出登录 ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html ------------------------------------- ======================================================================== 5 passed in 0.03s ========================================================================
每个函数都增加了前后置
为类增加夹具
创建test_demo02.py文件,这里
scope
定义为class
import pytest @pytest.fixture(scope='class', autouse=True) def login(): print("登录系统") yield print("退出登录") class TestCase2: def test_03(self): print("测试用例3") def test_04(self): print("测试用例4") class TestCase3: def test_05(self): print("测试用例5") def test_06(self): print("测试用例6") if __name__ == '__main__': pytest.main()
执行测试:
$ pytest test_02_demo.py ======================================================================= test session starts ======================================================================= platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'} rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4 collected 4 items test_02_demo.py::TestCase2::test_03 登录系统 测试用例3 PASSED test_02_demo.py::TestCase2::test_04 测试用例4 PASSED退出登录 test_02_demo.py::TestCase3::test_05 登录系统 测试用例5 PASSED test_02_demo.py::TestCase3::test_06 测试用例6 PASSED退出登录 ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html ------------------------------------- ======================================================================== 4 passed in 0.03s ========================================================================
两个类的执行都加上了前后置。
若只想为其中一个类添加前后置,需要去掉
autouse
参数,在需要增加前后置的类用@fixture.mark.usefixtures()
装饰器来实现,代码如下:import pytest @pytest.fixture(scope='class') def login(): print("登录系统") yield print("退出登录") class TestCase2: def test_03(self): print("测试用例3") def test_04(self): print("测试用例4") @pytest.mark.usefixtures("login") class TestCase3: def test_05(self): print("测试用例5") def test_06(self): print("测试用例6") if __name__ == '__main__': pytest.main()
执行测试:
$ pytest test_02_demo.py ======================================================================= test session starts ======================================================================= platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'} rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4 collected 4 items test_02_demo.py::TestCase2::test_03 测试用例3 PASSED test_02_demo.py::TestCase2::test_04 测试用例4 PASSED test_02_demo.py::TestCase3::test_05 登录系统 测试用例5 PASSED test_02_demo.py::TestCase3::test_06 测试用例6 PASSED退出登录 ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html ------------------------------------- ======================================================================== 4 passed in 0.03s ========================================================================
只有被
@fixture.mark.usefixtures()
装饰器修饰的类增加了前后置
为模块增加夹具
创建test_demo03.py文件,这里
scope
定义为module
(当前的py文件)import pytest @pytest.fixture(scope='module', autouse=True) def login(): print("登录系统") yield print("退出登录") class TestCase4: def test_07(self): print("测试用例7") def test_08(self): print("测试用例8") class TestCase5: def test_09(self): print("测试用例9") def test_10(self): print("测试用例10") def test_11(): print("测试用例11") if __name__ == '__main__': pytest.main()
执行测试:
$ pytest test_03_demo.py ======================================================================= test session starts ======================================================================= platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'} rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4 collected 5 items test_03_demo.py::TestCase4::test_07 登录系统 测试用例7 PASSED test_03_demo.py::TestCase4::test_08 测试用例8 PASSED test_03_demo.py::TestCase5::test_09 测试用例9 PASSED test_03_demo.py::TestCase5::test_10 测试用例10 PASSED test_03_demo.py::test_11 测试用例11 PASSED退出登录 ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html ------------------------------------- ======================================================================== 5 passed in 0.03s ========================================================================
文件执行的开始和结尾增加了前后置处理
为会话增加夹具
scope=session
的情况基本是跨多个py文件来执行的,需要配合conftest.py
来实现,会在下面的内容中进行介绍。
fixture其他参数
params
在test_demo02.py的fixture()
中增加参数params
,这里模拟的是登录,多个用户切换登录测试。
import pytest
@pytest.fixture(scope='class', params=['张三', '李四', '王五'])
def login(request): // 参数request是固定写法,必须是request
print("登录系统")
yield request.param // 前置处理完毕后回传request.param的值,这里的yield相当于return
print("退出登录")
class TestCase2:
def test_03(self, login):
print("测试用例3")
print(login)
def test_04(self):
print("测试用例4")
@pytest.mark.usefixtures("login")
class TestCase3:
def test_05(self):
print("测试用例5")
def test_06(self):
print("测试用例6")
if __name__ == '__main__':
pytest.main()
上面代码中,类TestCase2
中函数test_03
增加了对fixturn()
的调用,类TestCase3
增加了对fixture()
的调用。
执行测试:
$ pytest test_02_demo.py
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 10 items
test_02_demo.py::TestCase2::test_03[\u5f20\u4e09] 登录系统
测试用例3
张三
PASSED
test_02_demo.py::TestCase2::test_03[\u674e\u56db] 退出登录
登录系统
测试用例3
李四
PASSED
test_02_demo.py::TestCase2::test_03[\u738b\u4e94] 退出登录
登录系统
测试用例3
王五
PASSED
test_02_demo.py::TestCase2::test_04 测试用例4
PASSED退出登录
test_02_demo.py::TestCase3::test_05[\u5f20\u4e09] 登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[\u5f20\u4e09] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[\u674e\u56db] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[\u674e\u56db] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[\u738b\u4e94] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[\u738b\u4e94] 测试用例6
PASSED退出登录
------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
======================================================================= 10 passed in 0.04s ========================================================================
调用了fixture()
的函数或类都对参数params
进行了遍历输出执行
ids
在test_demo02.py的fixture()
中增加参数ids
,ids
是与parmas
一一对应的,不设置ids
,pytest会自动设置一个ids
,这里将ids
设置成为3个用户名的拼音,代码如下:
import pytest
@pytest.fixture(scope='class', params=['张三', '李四', '王五'], ids=['zhangsan', 'lisi', 'wangwu'])
def login(request):
print("登录系统")
yield request.param
print("退出登录")
class TestCase2:
def test_03(self, login):
print("测试用例3")
print(login)
def test_04(self):
print("测试用例4")
@pytest.mark.usefixtures("login")
class TestCase3:
def test_05(self):
print("测试用例5")
def test_06(self):
print("测试用例6")
if __name__ == '__main__':
pytest.main()
执行测试:
$ pytest test_02_demo.py
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 10 items
test_02_demo.py::TestCase2::test_03[zhangsan] 登录系统
测试用例3
张三
PASSED
test_02_demo.py::TestCase2::test_03[lisi] 退出登录
登录系统
测试用例3
李四
PASSED
test_02_demo.py::TestCase2::test_03[wangwu] 退出登录
登录系统
测试用例3
王五
PASSED
test_02_demo.py::TestCase2::test_04 测试用例4
PASSED退出登录
test_02_demo.py::TestCase3::test_05[zhangsan] 登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[zhangsan] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[lisi] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[lisi] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[wangwu] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[wangwu] 测试用例6
PASSED退出登录
------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
======================================================================= 10 passed in 0.04s ========================================================================
用例的ids都显示成了用户名的拼音
name
在test_demo02.py的fixture()
中增加参数name
,name
是将fixture()
装饰的函数改了调用时的名称,设置name
后,无法再用原来的名字调用,比如这里的login
就是失效了,代码如下:
import pytest
@pytest.fixture(scope='class', params=['张三', '李四', '王五'], ids=['zhangsan', 'lisi', 'wangwu'], name='denglu')
def login(request):
print("登录系统")
yield request.param
print("退出登录")
class TestCase2:
def test_03(self, login):
print("测试用例3")
print(login)
def test_04(self):
print("测试用例4")
@pytest.mark.usefixtures("login")
class TestCase3:
def test_05(self):
print("测试用例5")
def test_06(self):
print("测试用例6")
if __name__ == '__main__':
pytest.main()
执行测试:
$ pytest test_02_demo.py
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 4 items
test_02_demo.py::TestCase2::test_03 ERROR
test_02_demo.py::TestCase2::test_04 测试用例4
PASSED
test_02_demo.py::TestCase3::test_05 ERROR
test_02_demo.py::TestCase3::test_06 ERROR
============================================================================= ERRORS ==============================================================================
_______________________________________________________________ ERROR at setup of TestCase2.test_03 _______________________________________________________________
file /Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py, line 16
def test_03(self, login):
E fixture 'login' not found
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, denglu, doctest_namespace, extra, include_metadata_in_junit_xml, metadata, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, testrun_uid, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py:16
_______________________________________________________________ ERROR at setup of TestCase3.test_05 _______________________________________________________________
file /Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py, line 26
def test_05(self):
E fixture 'login' not found
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, denglu, doctest_namespace, extra, include_metadata_in_junit_xml, metadata, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, testrun_uid, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py:26
_______________________________________________________________ ERROR at setup of TestCase3.test_06 _______________________________________________________________
file /Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py, line 29
def test_06(self):
E fixture 'login' not found
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, denglu, doctest_namespace, extra, include_metadata_in_junit_xml, metadata, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, testrun_uid, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
> use 'pytest --fixtures [testpath]' for help on them.
/Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py:29
------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
===================================================================== short test summary info =====================================================================
ERROR test_02_demo.py::TestCase2::test_03
ERROR test_02_demo.py::TestCase3::test_05
ERROR test_02_demo.py::TestCase3::test_06
=================================================================== 1 passed, 3 errors in 0.03s ===================================================================
调用到login的地址都报错了。将代码中调用的login改成denglu,代码如下:
import pytest
@pytest.fixture(scope='class', params=['张三', '李四', '王五'], ids=['zhangsan', 'lisi', 'wangwu'], name='denglu')
def login(request):
print("登录系统")
yield request.param
print("退出登录")
class TestCase2:
def test_03(self, denglu):
print("测试用例3")
print(denglu)
def test_04(self):
print("测试用例4")
@pytest.mark.usefixtures("denglu")
class TestCase3:
def test_05(self):
print("测试用例5")
def test_06(self):
print("测试用例6")
if __name__ == '__main__':
pytest.main()
执行测试:
$ pytest test_02_demo.py
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 10 items
test_02_demo.py::TestCase2::test_03[zhangsan] 登录系统
测试用例3
张三
PASSED
test_02_demo.py::TestCase2::test_03[lisi] 退出登录
登录系统
测试用例3
李四
PASSED
test_02_demo.py::TestCase2::test_03[wangwu] 退出登录
登录系统
测试用例3
王五
PASSED
test_02_demo.py::TestCase2::test_04 测试用例4
PASSED退出登录
test_02_demo.py::TestCase3::test_05[zhangsan] 登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[zhangsan] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[lisi] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[lisi] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[wangwu] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[wangwu] 测试用例6
PASSED退出登录
------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
======================================================================= 10 passed in 0.04s ========================================================================
conftest.py结合fixture
- conftest.py是专门用于存放fixture的配置文件,名字是固定的,不能变;
- 在conftest.py文件里的所有方法在调用时都不需要导入(会自动查找);
- conftest.py文件可以有多个,并且多个conftest.py文件里的多个fixture可以被一个用例调用;
- conftest.py与运行的用例要在同一个package下,并有init.py文件
举例演示:
项目根目录
项目级conftest.py
登录/登出 fixture()
资质管理目录
- 模块级conftest.py
- 资质管理fixture()
- 资质管理模块测试用例.py
- 模块级conftest.py
下单管理目录
- 模块级conftest.py
- 下单管理fixture()
- 下单管理模块测试用例.py
- 模块级conftest.py
目录结构如下:
- /
- test_zizhi
- __ init __.py
- conftest.py
- test_demo01.py
- test_xiadan
- __ init __.py
- conftest.py
- test_demo02.py
- conftest.py
- test_zizhi
根据上面的目录结构,创建包、文件
根目录conftest.py代码如下:
import pytest
@pytest.fixture(scope='class', autouse=True)
def login():
print("登录系统")
yield
print("退出登录")
test_zizhi/conftest.py代码如下:
import pytest
@pytest.fixture(scope='class', autouse=True)
def zizhi():
print("进入资质管理")
yield
print("退出资质管理")
test_zizhi/test_demo01.py代码如下:
class TestCase1:
def test_01(self):
print("资质管理用例01")
def test_02(self):
print("资质管理用例02")
test_xiadan/conftest.py代码如下:
import pytest
@pytest.fixture(scope='class', autouse=True)
def xiada():
print("开始下单")
yield
print("完成下单")
test_xiadan/test_demo02.py代码如下:
class TestCase2:
def test_11(self):
print("下单用例11")
def test_12(self):
print("下单用例12")
在命令中,输入pytest执行用例,结果如下:
$ pytest
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini, testpaths: test_zizhi, test_xiadan
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 4 items
test_zizhi/test_demo01.py::TestCase1::test_01 登录系统
进入资质管理
资质管理用例01
PASSED
test_zizhi/test_demo01.py::TestCase1::test_02 资质管理用例02
PASSED退出资质管理
退出登录
test_xiadan/test_demo02.py::TestCase2::test_11 登录系统
开始下单
下单用例11
PASSED
test_xiadan/test_demo02.py::TestCase2::test_12 下单用例12
PASSED完成下单
退出登录
------------------------------------------- generated html file: file:///Users/laobai/workspaces/testdemo01/report.html -------------------------------------------
======================================================================== 4 passed in 0.03s ========================================================================
可以看到测试结果中,资质管理的用例和下单的用例没有连续执行,而是退出登录,再次登录后才执行的。
这里将根目录下的conftest.py
中fixture
中的scope
的值由class
改为session
或package
import pytest
@pytest.fixture(scope='session', autouse=True)
def login():
print("登录系统")
yield
print("退出登录")
再次执行:
$ pytest
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini, testpaths: test_zizhi, test_xiadan
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 4 items
test_zizhi/test_demo01.py::TestCase1::test_01 登录系统
进入资质管理
资质管理用例01
PASSED
test_zizhi/test_demo01.py::TestCase1::test_02 资质管理用例02
PASSED退出资质管理
test_xiadan/test_demo02.py::TestCase2::test_11 开始下单
下单用例11
PASSED
test_xiadan/test_demo02.py::TestCase2::test_12 下单用例12
PASSED完成下单
退出登录
------------------------------------------- generated html file: file:///Users/laobai/workspaces/testdemo01/report.html -------------------------------------------
======================================================================== 4 passed in 0.03s ========================================================================
以上所有用例只需登录一次即可全部测试完成。在实际工作中可以更加不同需要灵活设置用例的前后置。
总结
pytest执行过程
- 查询项目根目录下的conftest.py文件;
- 查询项目根目录下的pytest.ini文件;
- 查询用例目录下的conftest.py文件;
- 查询测试用例py文件中是否有setup_class、teardown_class、setup、teardown;
- 再根据pytest.ini文件的测试用例规则去查找用例并执行。