这篇笔记使用了上海-悠悠博客中的例子,详见https://www.cnblogs.com/yoyoketang,仅供自己查阅
本篇涉及fixture,参数化parametrizing,终结函数、fixture接受parametrizing参数、skip跳过用例
fixture
pytest有setup和teardown的用法,不过更推荐使用fixture的方式,优势在于:
- 命名方式灵活,不局限于setup和teardown这几个命名;
- 可以控制某些用例进行setup和teardown,因为有的用例并不需要或不应该进行setup和teardown;
- conftest.py 配置里可以实现数据共享,不需要import就能自动找到一些配置;
- scope="module" 可以实现多个.py跨文件共享前置, 每一个.py文件调用一次;
- scope="session" 以实现多个.py跨文件使用一个session来完成多个用例;
fixture参数传入举个栗子(使用上海悠悠的栗子)
import pytest
# 不带参数时默认scope="function"
@pytest.fixture()
def login():
print("输入账号,密码先登录")
def test_s1(login):
print("用例1:登录之后其它动作111")
def test_s2(): # 不传login
print("用例2:不需要登录,操作222")
def test_s3(login):
print("用例3:登录之后其它动作333")
if __name__ == "__main__":
pytest.main(["-s", "test_fix.py"])
-----------------------输出------------------------
platform win32 -- Python 3.6.0, pytest-3.6.3, py-1.5.4, pluggy-0.6.0
rootdir: E:\YOYO, inifile:
collected 3 items
test_fix.py 输入账号,密码先登录
用例1:登录之后其它动作111
.用例2:不需要登录,操作222
.输入账号,密码先登录
用例3:登录之后其它动作333
conftest.py配置
conftest.py配置好后可以使setup和teardown的操作生效于文件所在同一个package下的用例中
注意点
- conftest.py配置脚本名称是固定的,不能改名称
- conftest.py与运行的用例要在同一个pakage下,并且有init.py文件
- 不需要import导入 conftest.py,pytest用例会自动查找
上海悠悠的栗子
# conftest.py
# coding:utf-8
import pytest
@pytest.fixture()
def login():
print("输入账号,密码先登录")
test_fix1.py
# coding:utf-8
import pytest
def test_s1(login):
print("用例1:登录之后其它动作111")
def test_s2(): # 不传login
print("用例2:不需要登录,操作222")
def test_s3(login):
print("用例3:登录之后其它动作333")
if __name__ == "__main__":
pytest.main(["-s", "test_fix1.py"])
test_fix2.py
# coding:utf-8
import pytest
def test_s4(login):
print("用例4:登录之后其它动作111")
def test_s5(): # 不传login
print("用例5:不需要登录,操作222")
if __name__ == "__main__":
pytest.main(["-s", "test_fix2.py"])
单独运行test_fix1.py和test_fix2.py都能调用到login()方法
终结函数关闭浏览器
yield和addfinalizer方法都是在测试完成后呼叫相应的代码,我理解都是用例执行前先执行yield和addfinalizer方法前的部分,在用例都执行完毕后开始执行yield和addfinalizer后的语句。
@pytest.fixture(scope='session', autouse=True)
def browser(request):
global driver
if driver is None:
driver = webdriver.Firefox()
def end():
driver.quit()
request.addfinalizer(end)
return driver
调用fixture的三种方法
- 函数或类里面方法直接传fixture的函数参数名称
例如:
# content of test_06.py
import time
import pytest
# ** 作者:上海-悠悠 QQ交流群:588402570**
@pytest.fixture(scope="function")
def start(request):
print('\n-----开始执行function----')
def test_a(start): # 在每个用例中传入形参start
print("-------用例a执行-------")
class Test_aaa():
def test_01(self, start): # 在每个用例中传入形参start
print('-----------用例01--------------')
def test_02(self, start): # 在每个用例中传入形参start
print('-----------用例02------------')
if __name__ == "__main__":
pytest.main(["-s", "test_06.py"])
- 使用装饰器@pytest.mark.usefixtures()修饰
# content of test_07.py
import time
import pytest
# ** 作者:上海-悠悠 QQ交流群:588402570**
@pytest.fixture(scope="function")
def start(request):
print('\n-----开始执行function----')
@pytest.mark.usefixtures("start") # 每个用例都用@pytest.mark.usefixtures()修饰
def test_a():
print("-------用例a执行-------")
@pytest.mark.usefixtures("start") # 每个用例都用@pytest.mark.usefixtures()修饰,只需要在类上修饰
class Test_aaa():
def test_01(self):
print('-----------用例01--------------')
def test_02(self):
print('-----------用例02------------')
if __name__ == "__main__":
pytest.main(["-s", "test_07.py"])
- autouse=True自动使用
- start设置scope为module级别,在当前.py用例模块只执行一次,autouse=True自动使用
- open_home设置scope为function级别,每个用例前都调用一次,自动使用
# content of test_08.py
import time
import pytest
# ** 作者:上海-悠悠 QQ交流群:588402570**
@pytest.fixture(scope="module", autouse=True) # 在当前py文件执行一次
def start(request):
print('\n-----开始执行moule----')
print('module : %s' % request.module.__name__)
print('----------启动浏览器---------')
yield
print("------------结束测试 end!-----------")
@pytest.fixture(scope="function", autouse=True) # 在每个用例执行一次
def open_home(request):
print("function:%s \n--------回到首页--------" % request.function.__name__)
def test_01():
print('-----------用例01--------------')
def test_02():
print('-----------用例02------------')
if __name__ == "__main__":
pytest.main(["-s", "test_08.py"])
也可在类中使用
import time
import pytest
# ** 作者:上海-悠悠 QQ交流群:588402570**
@pytest.fixture(scope="module", autouse=True)
def start(request):
print('\n-----开始执行moule----')
print('module : %s' % request.module.__name__)
print('----------启动浏览器---------')
yield
print("------------结束测试 end!-----------")
class Test_aaa():
@pytest.fixture(scope="function", autouse=True)
def open_home(self, request):
print("function:%s \n--------回到首页--------" % request.function.__name__)
def test_01(self):
print('-----------用例01--------------')
def test_02(self):
print('-----------用例02------------')
if __name__ == "__main__":
pytest.main(["-s", "test_09.py"])
- 如果用例需要用到多个fixture的返回数据,fixture也可以return一个元组、list或字典,然后从里面取出对应数据;
- 当然也可以分开定义成多个fixture,然后test用例传多个fixture参数;
- fixture与fixture直接也能互相调用的,这条举个例子。
import pytest
@pytest.fixture()
def first():
print("获取用户名")
a = "yoyo"
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] == "yoyo"
if __name__ == "__main__":
pytest.main(["-s", "test_fixture6.py"])
参数化parametrizing
pytest.mark.parametrize可以实现测试用例的参数化,可以理解为这个装饰器相当于将参数都传进来后依次for循环执行,注意:
- 该装饰器中传递两个参数,第一个参数是字符串,若有多个参数则用逗号分开
- 第二个参数为list,当有多组数据时用每组数据以元祖形式存放
- 装饰器修饰的函数使用的形参名要与该装饰器中第一个参数的形参名一致
多说无益,还是看例子
# 这里是一个实现检查一定的输入和期望输出测试功能的典型例子
import pytest
@pytest.mark.parametrize("test_input,expected",
[ ("3+5", 8),
("2+4", 6),
("6 * 9", 42),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
if __name__ == "__main__":
pytest.main(["-s", "test_canshu1.py"])
-----------------------------输出结果-------------------------------
================================== FAILURES ===================================
_____________________________ test_eval[6 * 9-42] _____________________________
test_input = '6 * 9', expected = 42
@pytest.mark.parametrize("test_input,expected",
[ ("3+5", 8),
("2+4", 6),
("6 * 9", 42),
])
def test_eval(test_input, expected):
> assert eval(test_input) == expected
E AssertionError: assert 54 == 42
E + where 54 = eval('6 * 9')
test_canshu1.py:11: AssertionError
===================== 1 failed, 2 passed in 1.98 seconds ======================
也可以标记单个测试实例参数化,例如使用内置的mark.xfail
# content of test_expectation.py
import pytest
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
pytest.param("6 * 9", 42, marks=pytest.mark.xfail), # 这里标记了预测失败,因此在执行完毕不会报错,只会返回xfail
])
def test_eval(test_input, expected):
print("-------开始用例------")
assert eval(test_input) == expected
if __name__ == "__main__":
pytest.main(["-s", "test_canshu1.py"])
------------------------------输出--------------------------
test_canshu1.py -------开始用例------
.-------开始用例------
.-------开始用例------
x
===================== 2 passed, 1 xfailed in 1.84 seconds =====================
若要对参数进行组合则堆叠装饰器
import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
print("测试数据组合:x->%s, y->%s" % (x, y))
if __name__ == "__main__":
pytest.main(["-s", "test_canshu1.py"])
-----------------------输出-----------------------
test_canshu1.py 测试数据组合:x->0, y->2
.测试数据组合:x->1, y->2
.测试数据组合:x->0, y->3
.测试数据组合:x->1, y->3
.
========================== 4 passed in 1.75 seconds ===========================
给参数意义
import pytest
data_1 = [
pytest.param(1,2,3, id='(a+b):pass'), # id自己定义,方便理解用例意义
pytest.param(4,5,10, id='(a+b):fail')
]
def add(a, b):
return a+b
class TestParametrize():
@pytest.mark.parametrize('a, b, expect', data_1)
def test_parametrize_1(self,a ,b, expect):
assert add(a, b) == expect
if __name__ == '__main__':
pytest.main(['-vs', 'testcase03.py'])
-----------------输出-----------------------
testcase03.py::TestParametrize::test_parametrize_1[(a+b):pass] PASSED
testcase03.py::TestParametrize::test_parametrize_1[(a+b):fail] FAILED
request参数
若@pytest.fixture装饰器中一些操作想要用到parametrize中的参数,传参得使用默认的request参数。使用request.param接收参数,举个例子
import pytest
#** 作者:上海-悠悠 QQ交流群:588402570**
# 测试账号数据
test_user_data = ["admin1", "admin2"]
@pytest.fixture(scope="module")
def login(request): # 使用request形参
user = request.param
print("登录账户:%s"%user)
return user
@pytest.mark.parametrize("login", test_user_data, indirect=True) # 添加indirect=True参数是为了把login当成一个函数去执行,而不是一个参数
def test_login(login):
'''登录用例'''
a = login
print("测试用例中login的返回值:%s" % a)
assert a != ""
if __name__ == "__main__":
pytest.main(["-s", "test_02.py"])
---------------------------输出-------------------------
..\..\..\..\..\..\YOYO\marktest\test_02.py 登录账户:admin1
测试用例中login的返回值:admin1
.登录账户:admin2
测试用例中login的返回值:admin2
request传2个参数
如果用到@pytest.fixture,里面用2个参数情况,可以把多个参数用一个字典去存储,这样最终还是只传一个参数,不同的参数再从字典里面取对应key值就行,如: user = request.param["user"]
举个例子
# coding:utf-8
import pytest
# ** 作者:上海-悠悠 QQ交流群:588402570**
# 测试账号数据
test_user_data = [{"user": "admin1", "psw": "111111"},
{"user": "admin1", "psw": ""}]
@pytest.fixture(scope="module")
def login(request):
user = request.param["user"]
psw = request.param["psw"]
print("登录账户:%s" % user)
print("登录密码:%s" % psw)
if psw:
return True
else:
return False
# indirect=True 声明login是个函数
@pytest.mark.parametrize("login", test_user_data, indirect=True)
def test_login(login):
'''登录用例'''
a = login
print("测试用例中login的返回值:%s" % a)
assert a, "失败原因:密码为空"
if __name__ == "__main__":
pytest.main(["-s", "test_03.py"])
--------------------------输出------------------------
..\..\..\..\..\..\YOYO\marktest\test_03.py 登录账户:admin1
登录密码:111111
测试用例中login的返回值:True
.登录账户:admin1
登录密码:
测试用例中login的返回值:False
F
login = False
@pytest.mark.parametrize("login", test_user_data, indirect=True)
def test_login(login):
'''登录用例'''
a = login
print("测试用例中login的返回值:%s" % a)
> assert a, "失败原因:密码为空"
E AssertionError: 失败原因:密码为空
E assert False
D:\YOYO\marktest\test_03.py:25: AssertionError
================================== FAILURES ===================================
_____________________________ test_login[login1] ______________________________
login = False
@pytest.mark.parametrize("login", test_user_data, indirect=True)
def test_login(login):
'''登录用例'''
a = login
print("测试用例中login的返回值:%s" % a)
> assert a, "失败原因:密码为空"
E AssertionError: 失败原因:密码为空
E assert False
D:\YOYO\marktest\test_03.py:25: AssertionError
===================== 1 failed, 1 passed in 0.05 seconds ======================
多个fixtrue
上面的用例是可以同时放多个fixture的,也就是多个前置操作,可以支持装饰器叠加,使用parametrize装饰器叠加时,用例组合是2个参数个数相乘。
举个例子
# coding:utf-8
import pytest
# 测试账号数据
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)
def test_login(input_user, input_psw):
'''登录用例'''
a = input_user
b = input_psw
print("测试数据a-> %s, b-> %s" % (a,b))
assert b
if __name__ == "__main__":
pytest.main(["-s", "test_04.py"])
-------------------------输出---------------------------
collected 4 items
test_04.py 登录账户:admin1
登录密码:11111
测试数据a-> admin1, b-> 11111
.登录账户:admin2
测试数据a-> admin2, b-> 11111
.登录密码:22222
测试数据a-> admin2, b-> 22222
.登录账户:admin1
测试数据a-> admin1, b-> 22222
========================== 4 passed in 0.05 seconds ===========================
skip跳过用例
- 无条件地跳过模块中的所有测试:
pytestmark = pytest.mark.skip("all tests still WIP")
- 根据某些条件跳过模块中的所有测试
pytestmark = pytest.mark.skipif(sys.platform == "win32", "tests for linux˓→ only"
- 如果缺少某些导入,则跳过模块中的所有测试
pexpect = pytest.importorskip("pexpect")