pytest基于类的用例方法与fixture组织
fixture介绍
fixture是pytest的核心功能,也是亮点功能,熟练掌握fixture的使用方法,pytest用起来才会得心应手!
fixture的目的是提供一个固定基线,在该基线上测试可以可靠地和重复地执行。fixture提供了区别于传统单元测试(setup/teardown)有显著改进:
- 有独立的命名,并通过声明它们从测试函数、模块、类或整个项目中的使用来激活。
- 按模块化的方式实现,每个fixture都可以互相调用。
- fixture的范围从简单的单元扩展到复杂的功能测试,允许根据配置和组件选项对fixture和测试用例进行参数化,或者跨函数
function、类class、模块module或整个测试会话session范围。
fixture用途
1.做测试前后的初始化设置,如测试数据准备,链接数据库,打开浏览器等这些操作都可以使用fixture来实现
2.测试用例的前置条件可以使用fixture实现
3.支持经典的xunit fixture ,像unittest使用的setup和teardown
4.fixture可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题
fixture实例
入门运用fixture
创建一个fixtureclass文件,并创建test_something.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pytest
class TestSomething:
# 我们希望运行test用例前运行“准备工作”,并在用例结束后运行“清理工作”,这时候用到fixture里的yield
@pytest.fixture() # autouse=True 自动使用
def setUp(self):
print("准备工作...")
yield
print("清理工作...")
# 每个用例以参数的形式把setUp传递进去,当运行用例时就能达到预期效果
def test_method_A(self,setUp):
print("测试类 > 用例方法A")
def test_method_B(self,setUp):
print("测试类 > 用例方法B")
def test_method_C(self,setUp):
print("测试类 > 用例方法C")
def test_method_D(self,setUp):
print("测试类 > 用例方法D")
if __name__ == '__main__':
pytest.main(['-v','-s','test_somthing.py'])
collecting ... collected 4 items test_somthing.py::TestSomething::test_method_A 准备工作... 测试类 > 用例方法A PASSED清理工作... test_somthing.py::TestSomething::test_method_B 准备工作... 测试类 > 用例方法B PASSED清理工作... test_somthing.py::TestSomething::test_method_C 准备工作... 测试类 > 用例方法C PASSED清理工作... test_somthing.py::TestSomething::test_method_D 准备工作... 测试类 > 用例方法D PASSED清理工作... ============================== 4 passed in 0.04s ==============================
如果我们不希望每个case都传入setUp,那就要用到autouse=True
如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pytest
class TestSomething:
# 我们希望运行test用例前运行“准备工作”,并在用例结束后运行“清理工作”,这时候用到fixture里的yield
@pytest.fixture(autouse=True) # autouse=True 自动使用
def setUp(self):
print("准备工作...")
yield
print("清理工作...")
def test_method_A(self):
print("测试类 > 用例方法A")
def test_method_B(self):
print("测试类 > 用例方法B")
def test_method_C(self):
print("测试类 > 用例方法C")
def test_method_D(self):
print("测试类 > 用例方法D")
if __name__ == '__main__':
pytest.main(['-v','-s','test_somthing.py'])
collecting ... collected 4 items test_somthing.py::TestSomething::test_method_A 准备工作... 测试类 > 用例方法A PASSED清理工作... test_somthing.py::TestSomething::test_method_B 准备工作... 测试类 > 用例方法B PASSED清理工作... test_somthing.py::TestSomething::test_method_C 准备工作... 测试类 > 用例方法C PASSED清理工作... test_somthing.py::TestSomething::test_method_D 准备工作... 测试类 > 用例方法D PASSED清理工作... ============================== 4 passed in 0.05s ==============================
进阶运用fixture
如果我们不想在class类里写fixture装饰器,这里用到第二种方法
通过conftest.py文件作为pytest的配置文件
conftest.py配置需要注意以下点:
- conftest.py配置脚本名称是固定的,不能改名称
- conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
- 不需要import导入 conftest.py,pytest用例会自动查找
conftest.py配置说明
- conftest.py文件名字是固定的,不可以做任何修改
- 不需要import导入conftest.py,pytest用例会自动识别该文件,若conftest.py文件放在根目录下,那么conftest.py作用于整个目录,全局调用
- 在不同的测试子目录也可以放conftest.py,其作用范围只在该层级以及以下目录生效
- 所有目录内的测试文件运行前都会先执行该目录下所包含的conftest.py文件
- conftest.py文件不能被其他文件导入
conftest.py与fixture结合
conftest文件实际应用中需要结合fixture来使用,如下
- conftest中fixture的scope参数为session时,那么整个测试在执行前会只执行一次该fixture
- conftest中fixture的scope参数为module时,那么每一个测试文件执行前都会执行一次conftest文件中的fixture
- conftest中fixture的scope参数为class时,那么每一个测试文件中的测试类执行前都会执行一次conftest文件中的fixture
- conftest中fixture的scope参数为function时,那么所有文件的测试用例执行前都会执行一次conftest文件中的fixture
conftest应用场景
- 测试中需共用到的token
- 测试中需共用到的测试用例数据
- 测试中需共用到的配置信息
- 结合 yield 语句,进行运行前环境的初始化和运行结束后环境的清理工作,yield前面的语句相当于unitest中的setup动作,yield后面的语句相当于unitest中的teardown动作,不管测试结果如何,yield后面的语句都会被执行。
- 当fixture超出范围时(即fixture返回值后,仍有后续操作),通过使用yield语句而不是return,来将值返回(因为return后,说明该函数/方法已结束,return后续的代码不会被执行),如下:
@pytest.fixture(scope="module")
def smtpConnection():
smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
yield smtp_connection # 返回 fixture 值smtp_connection
print("teardown smtp")
smtp_connection.close()
无论测试的异常状态如何,print和close()语句将在模块中的最后一个测试完成执行时执行。
- 可以使用with语句无缝地使用yield语法(with语句会自动释放资源)
@pytest.fixture(scope="module")
def smtpConnection():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp_connection # 返回smtp_connection对象值
测试结束后, 连接将关闭,因为当with语句结束时,smtp_connection对象会自动关闭。
应用场景
多个用例调用一个登陆功能,如果有多个.py的文件都需要调用这个登陆功能的话,那就不能把登陆写到用例里面去了。此时应该要有一个配置文件,单独管理一些预置的操作场景,pytest里面默认读取conftest.py里面的配置
conftest.py代码
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pytest
@pytest.fixture(scope="class")
def oneTimeSetUp():
print("类前准备工作...")
yield
print("类后清理工作...")
@pytest.fixture(autouse=True)
def setUp():
print("准备工作...")
yield
print("清理工作...")
test_something.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pytest
@pytest.mark.usefixtures("oneTimeSetUp") # 用mark指定oneTimeSetUp运用在该类下
class TestSomething:
def test_method_A(self):
print("测试类 > 用例方法A")
def test_method_B(self):
print("测试类 > 用例方法B")
def test_method_C(self):
print("测试类 > 用例方法C")
def test_method_D(self):
print("测试类 > 用例方法D")
if __name__ == '__main__':
pytest.main(['-v','-s','test_somthing.py'])
collecting ... collected 4 items test_somthing.py::TestSomething::test_method_A 类前准备工作... 准备工作... 测试类 > 用例方法A PASSED清理工作... test_somthing.py::TestSomething::test_method_B 准备工作... 测试类 > 用例方法B PASSED清理工作... test_somthing.py::TestSomething::test_method_C 准备工作... 测试类 > 用例方法C PASSED清理工作... test_somthing.py::TestSomething::test_method_D 准备工作... 测试类 > 用例方法D PASSED清理工作... 类后清理工作... ============================== 4 passed in 0.05s ==============================
fixture传参
返回多个数据
如果用例需要用到多个fixture的返回数据,fixture也可以return一个元组、list或字典,然后从里面取出对应数据。
import pytest
@pytest.fixture()
def user():
print("获取用户名")
a = "hjt"
b = "123456"
return (a, b)
def test_1(user):
u = user[0]
p = user[1]
print("测试账号:%s, 密码:%s" % (u, p))
assert u == "hjt"
用例传多个fixture参数
@pytest.fixture()
def user():
print("获取用户名")
a = "hjt"
return a
@pytest.fixture()
def psw():
print("获取密码")
b = "123456"
return b
def test_1(user, psw):
'''传多个fixture'''
print("测试账号:%s, 密码:%s" % (user, psw))
assert user == "hjt"
和parametrize组合使用
# 测试账号数据
test_user = ["admin1", "admin2"]
test_psw = ["11111", "22222"]
@pytest.fixture(scope="module")
def input_user(request):
user = request.param
print("登录账户:%s" % user)
return user
@pytest.fixture(scope="module")
def input_psw(request):
psw = request.param
print("登录密码:%s" % psw)
return psw
@pytest.mark.parametrize("input_user", test_user, indirect=True)
@pytest.mark.parametrize("input_psw", test_psw, indirect=True)
# 1) 当 indirect=False 时,argnames 参数被当成普通变量;
# 2) 当 indirect=True 时,parametrize 中的 argnames 参数被当成函数执行,且 argvalues 值作为 argnames函数中的参数传参
def test_login(input_user, input_psw):
"""登录用例"""
a = input_user
b = input_psw
print("测试数据a-> %s, b-> %s" % (a, b))
assert b
fixture之间互相调用
@pytest.fixture()
def first():
print("获取用户名")
a = "hjt"
return a
@pytest.fixture()
def sencond(first):
'''psw调用user fixture'''
a = first
b = "123456"
return (a, b)
def test_1(sencond):
'''用例传fixture'''
print("测试账号:%s, 密码:%s" % (sencond[0], sencond[1]))
assert sencond[0] == "hjt"

浙公网安备 33010602011771号