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"