pytest是一个python的单元测试框架,也称为用例框架。
作用:
1)发现测试用例。从多个py文件中按照一定的规则找到测试用例
2)执行测试用例
3)判断测试结果,运用python断言
4)生成测试报告,可以使用allure、pytest-html、pytest-testreport
1)py文件全部小写,多个英文用_隔开
2)class名首字母大写,驼峰
3)函数和方法名小写,多个英文用_隔开
4)全局变量,前面要加global
5)常量字母必须全大写,如:AGE_OF_NICK
1)模块名(py文件)必须是以test_开头或者_test结尾
2)测试类(class)必须以Test开头,并且不能带init方法,类里的方法必须以test_开头
3)测试用例(函数)必须以test_开头
import pytest
def test_01():
print("this is {} testcase".format(__name__))
if __name__=='__main__':
pytest.main()
main中可使用的参数有:
参数 | 描述 | 案例 |
---|---|---|
-v | 输出调试信息。如:打印信息 | pytest.main([‘-v’,‘testcase/test_one.py’,‘testcase/test_two.py’]) |
-s | 输出更详细的信息,如:文件名、用例名 | pytest.main([‘-vs’,‘testcase/test_one.py’,‘testcase/test_two.py’]) |
-n | 多线程或分布式运行测试用例 | |
-x | 只要有一个用例执行失败,就停止执行测试 | pytest.main([‘-vsx’,‘testcase/test_one.py’]) |
– maxfail | 出现N个测试用例失败,就停止测试 | pytest.main([‘-vs’,‘-x=2’,‘testcase/test_one.py’] |
–html=report.html | 生成测试报告 | pytest.main([‘-vs’,‘–html=./report.html’,‘testcase/test_one.py’]) |
-m | 通过标记表达式执行 | |
-k | 根据测试用例的部分字符串指定测试用例,可以使用and,or |
def test_a():
print("this is {} testcase".format('a'))
assert 1==1
def test_b():
print("this is {} testcase".format('b'))
assert 1==1
def test_c():
print("this is {} testcase".format('c'))
#终端输入:pytest ./testcase/test_one.py --html=./report/report.html
main中可使用的参数有:
参数 | 描述 | 案例 |
---|---|---|
-v | 输出调试信息。如:打印信息 | pytest -x ./testcase/test_one.py |
-q | 输出简单信息。 | pyets -q ./testcase/test_one.py |
-s | 输出更详细的信息,如:文件名、用例名 | pytest -s ./testcase/test_one.py |
-n | 多线程或分布式运行测试用例 | |
-x | 只要有一个用例执行失败,就停止执行测试 | pytest -x ./testcase/test_one.py |
– maxfail | 出现N个测试用例失败,就停止测试 | pytest --maxfail=2 ./testcase/test_one.py |
–html=report.html | 生成测试报告 | pytest ./testcase/test_one.py --html=./report/report.html |
-m | 通过标记表达式执行 | pytest -m slow ./testcase/test_one.py #@pytest.mark.slow装饰的所有用例 |
-k | 根据测试用例的部分字符串指定测试用例,可以使用and,or | pytest -k “MyClass and not method”,这条命令会匹配文件名、类名、方法名匹配表达式的用例,这里这条命令会运行 TestMyClass.test_something, 不会执行 TestMyClass.test_method_simple |
注意: | ||
如果只执行 pytest ,会查找当前目录及其子目录下以test_.py或_test.py文件,找到文件后,在文件中找到以 test 开头函数并执行 |
不管是mian执行方式还是命令执行,最终都会去读取pytest.ini文件
在项目的根目录下创建pytest.ini文件
[pytest]
addopts=-vs -m slow --html=./report/report.html
testpaths=testcase
test_files=test_*.py
test_classes=Test*
test_functions=test_*
makerers=
smock:冒烟测试用例
参数 | 作用 |
---|---|
[pytest] | 用于标志这个文件是pytest的配置文件 |
addopts | 命令行参数,多个参数之间用空格分隔 |
testpaths | 配置搜索参数用例的范围 |
python_files | 改变默认的文件搜索规则 |
python_classes | 改变默认的类搜索规则 |
python_functions | 改变默认的测试用例的搜索规则 |
markers | 用例标记,自定义mark,需要先注册标记,运行时才不会出现warnings |
pytset.ini文件尽可能不要出现中文。
可以理解成一个专门存放fixture的配置文件
例如:多个测试用例文件(test_*.py)的所有用例都需要用登录功能来作为前置操作,那就不能把登录功能写到某个用例文件中去了
最顶层的 conftest,一般写全局的 fixture,比如:在Web UI 自动化中,可能会写初始化 driver 的 fixture
./conftest.py
import pytest
@pytest.fixture(scope="session",autouse=True)
def login():
print("====根目录的session登录功能,返回账号===")
name = "testyy"
token = "npoi213bn4"
yield name, token
print("====根目录的session退出登录!!!====")
./testcase/conftest.py
import pytest
@pytest.fixture(scope="module", autouse=True)
def login_test_cse():
print("====testcase/module目录的登录功能,返回账号===")
name = "testyy"
token = "npoi213bn4"
yield name, token
print("====testcase/module目录的退出登录!!!====")
./testcase/test_three.py
def test_a():
print(f"=====test_three::test_a=====")
def test_b():
print(f"=====test_three::test_b=====")
./testcase/testone/conftest.py
import pytest
@pytest.fixture(scope="module",autouse=True)
def login_test_one():
print("====testcase/testone/moduled目录的登录功能,返回账号,token===")
name = "testyy"
token = "npoi213bn4"
yield name, token
print("====testcase/testone/moduled目录的退出登录!!!====")
./testcase/testone/test_one.py
def test_a():
print(f"=====test_one::test_a=====")
def test_b():
print(f"=====test_one::test_b=====")
./testcase/testone/test_two.py
def test_a():
print(f"=====test_one::test_a=====")
def test_b():
print(f"=====test_one::test_b=====")
./run_test.py
import pytest
if __name__ == '__main__':
pytest.main(["-s", "./testcase/"])
pytest -x # 第01次失败,就停止测试
pytest --maxfail=2 # 出现2个失败就终止测试
pytest ./testcase/test_one.py
pytest ./testcase/
pytest -k "MyClass and not method"
#这条命令会匹配文件名、类名、方法名匹配表达式的用例,这里这条命令会运行 TestMyClass.test_something, 不会执行 TestMyClass.test_method_simple
nodeid由模块文件名、分隔符、类名、方法名、参数构成,举例如下:
#运行指定模块的指定函数
pytest ./testcase/test_one.py::test_a
pytest ./testcase/test_one.py::test_a ./testcase/test_one.py::test_b
#运行指定模块的指定类中的方法
pytest test_mod.py::TestClass::test_method
pytest -m smock
#这条命令会执行被装饰器 @pytest.mark.smock 装饰的所有测试用例
pytest --pyargs pkg.testing
#这条命令会自动导入包 pkg.testing,并使用该包所在的目录,执行下面的用例。
#需要先安装pytest-xdist
#pip install -U pytest-xdist
pytest test_one.py -n NUM
#NUM填写并发的进程数
在做接口测试时,有事会遇到503或短时的网络波动,导致case运行失败,而这并非是我们期望的结果,此时可以就可以通过重试运行cases的方式来解决。
#需要先安装pytest-rerunfailures
#pip install -U pytest-rerunfailures
pytest test_se.py --reruns NUM
#NUM 表示失败后的重试次数
默认执行顺序从上到下,改变测试用例的执行顺序,在测试用例上加上标记,实现如下:
1)先安装pytest-ordering:pip install pytest-ordering
2)在测试用例上打标记:pytest.mark.run(order=1)
import pytest
@pytest.mark.run(order=3)
def test_a():
print("this is {} testcase".format('a'))
assert 1==1
@pytest.mark.run(order=1)
def test_b():
print("this is {} testcase".format('b'))
assert 1==1
@pytest.mark.run(order=2)
def test_c():
print("this is {} testcase".format('c'))
if __name__=='__main__':
pytest.main()
执行结果:
test_one.py::test_b this is b testcase
PASSED
test_one.py::test_c this is c testcase
PASSED
test_one.py::test_a this is a testcase
PASSED
1.setup和teardown主要分为:模块级别、类级别、函数级别、方法级别、方法细化级别,分别如下:
方法 | 描述 |
---|---|
setup_module() | 在每个模块之前执行 |
teardown_module() | 在每个模块之后执行 |
setup_class() | 在每个类之前执行,即:在一个测试类只运行一次setup_class和teardown_class,不关心测试类内有多少个测试函数。 |
teardown_class() | 在每个类之后执行,即:在一个测试类只运行一次setup_class和teardown_class,不关心测试类内有多少个测试函数。 |
setup_function() | 在每个函数之前执行。 |
teardown_function() | 在每个函数之后执行。 |
setup_method() | 在每个方法之前执行 |
teardown_method() | 在每个方法之后执行 |
setup() | 在每个方法之前执行 |
teardown() | 在每个方法之后执行 |
2.扩展:在unittest中前后置只有setup和teardwon、setupClass和teardwonClass两大类。 |
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@Time:2022-10-0712:06
@Auth:ting.chun
@File:test_one.py.py
@IDE:PyCharm
"""
import pytest
def setup_module():
print("=====整个.py模块开始前只执行一次:打开浏览器=====")
def teardown_module():
print("=====整个.py模块结束后只执行一次:关闭浏览器=====")
def setup_function():
print("===每个函数级别用例开始前都执行setup_function===")
def teardown_function():
print("===每个函数级别用例结束后都执行teardown_function====")
def test_one():
print("one")
def test_two():
print("two")
class TestCase():
def setup_class(self):
print("====整个测试类开始前只执行一次setup_class====")
def teardown_class(self):
print("====整个测试类结束后只执行一次teardown_class====")
def setup_method(self):
print("==类里面每个用例执行前都会执行setup_method==")
def teardown_method(self):
print("==类里面每个用例结束后都会执行teardown_method==")
def setup(self):
print("=类里面每个用例执行前都会执行setup=")
def teardown(self):
print("=类里面每个用例结束后都会执行teardown=")
def test_three(self):
print("three")
def test_four():
print("four")
if __name__ == '__main__':
pytest.main(["-q", "-s", "-ra", "test_one.py"])
# 运行测试用例,显示总的结果信息,使用命令 pytest -ra
执行结果如下:
fixture修饰器来标记固定的工厂函数,在其他函数,模块,类或整个工程调用它时会被激活并优先执行,通常会被用于完成预置处理和重复操作。
方法:fixture(scope="function", params=None, autouse=False, ids=None, name=None)
常用参数:
scope:被标记方法的作用域
"function" (default):作用于每个测试方法,每个test都运行一次
"class":作用于整个类,每个class的所有test只运行一次
"module":作用于整个模块,每个module的所有test只运行一次
"session:作用于整个session(慎用),每个session只运行一次,即整个测试会话,即开始执行 Pytest 到结束测试
params:(list类型)提供参数数据,供调用标记方法的函数使用
autouse:是否自动运行,默认为False不运行,设置为True自动运行
执行的三种方式:
扩展知识点:
将fixture名称作为测试用例的输入参数:
class Test_ABC:
@pytest.fixture()
def before(self):
print("------->before")
def test_a(self,before): # ️ test_a方法传入了被fixture标识的函数,以参数的形式
print("------->test_a")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 发现before会优先于测试函数运行
------->test_a
使用装饰器pytest.mark.usefixtures(fixturename)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@Time:2022-10-0712:06
@Auth:ting.chun
@File:test_one.py.py
@IDE:PyCharm
"""
import pytest
@pytest.fixture() # fixture标记的函数可以应用于测试类外部
def before():
print("------->before")
@pytest.mark.usefixtures("before")
class TestOne:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 1
if __name__ == '__main__':
pytest.main("-s test_one.py")
执行结果:
test_one.py::TestOne::test_a
------->before # 发现before自动优先于setup运行
------->setup
------->test_a
PASSED
test_one.py::TestOne::test_b ------->before
------->setup
------->test_b
PASSED
fixture设置autouse=True
import pytest
@pytest.fixture(autouse=True) # 设置为默认运行
def before():
print("------->before")
class Test_ABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 发现before自动优先于测试类运行
------->setup
------->test_a
fixture的作用域设置为:scope=“function”
import pytest
@pytest.fixture(scope='function',autouse=True) # 作用域设置为function,自动运行
def before():
print("------->before")
class Test_ABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 运行第一次
------->setup
------->test_a
.------->before # 运行第二次
------->setup
------->test_b
fixture的作用域设置为:scope=“class”
import pytest
@pytest.fixture(scope='class',autouse=True) # 作用域设置为class,自动运行
def before():
print("------->before")
class Test_ABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 发现只运行一次
------->setup
------->test_a
.
------->setup
------->test_b
.
fixture的返回值
import pytest
@pytest.fixture()
def need_data():
return 2 # 返回数字2
class Test_ABC:
def test_a(self,need_data):
print("------->test_a")
assert need_data != 3 # 拿到返回值做一次断言
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->test_a
.
import pytest
@pytest.fixture(params=[1, 2, 3])
def need_data(request): # 传入参数request 系统封装参数
return request.param # 取列表中单个值,默认的取值方式
class Test_ABC:
def test_a(self,need_data):
print("------->test_a")
assert need_data != 3 # 断言need_data不等于3
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
# 可以发现结果运行了三次
test_abc.py
1
------->test_a
.
2
------->test_a
.
3
------->test_a
F
import pytest
@pytest.fixture(scope="session")
def open():
# 会话前置操作setup
print("===打开浏览器===")
test = "test"
yield test
# 会话后置操作teardown
print("==关闭浏览器==")
@pytest.fixture
def login(open):
# 方法级别前置操作setup
print(f"open方法yield的返回结果:{open}")
name = "==我是账号=="
pwd = "==我是密码=="
# 返回变量
yield name, pwd
# 方法级别后置操作teardown
print("登录成功")
def test_s1(login):
print("==用例1==")
# 返回的是一个元组
print(login)
# 分别赋值给不同变量
name, pwd = login
print(name, pwd)
assert "账号" in name
assert "密码" in pwd
def test_s2(login):
print("==用例2==")
print(login)
if __name__ == '__main__':
pytest.main("-s test_one.py")
执行结果:
test_one.py::test_s1 ===打开浏览器===
open方法yield的返回结果:test
==用例1==
('==我是账号==', '==我是密码==')
==我是账号== ==我是密码==
PASSED登录成功
test_one.py::test_s2 open方法yield的返回结果:test
==用例2==
('==我是账号==', '==我是密码==')
PASSED登录成功
==关闭浏览器==
yield和with结合使用
# 官方例子
@pytest.fixture(scope="module")
def smtp_connection():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp_connection # provide the fixture value
该 smtp_connection 连接将测试完成执行后已经关闭,因为smtp_connection 对象自动关闭时, with 语句结束
传单个参数
import pytest
@pytest.fixture()
def pre_data(request):
name = request.param
print(f"== 账号是:{name} ==")
return name
data = ["pyy1", "polo"]
ids = [f"pre_test_name is:{name}" for name in data]
#添加indirect=True参数是为了把 login 当成一个函数去执行,而不是一个参数,并且将 data 当做参数传入函数
#def test_name(login) ,这里的 login 是获取 fixture 返回的值
@pytest.mark.parametrize("pre_data", data, ids=ids, indirect=True)
def test_name(pre_data):
print(f" 测试用例的登录账号是:{pre_data} ")
多个参数
@pytest.fixture()
def pre_data(request):
param = request.param
print(f"账号是:{param['username']},密码是:{param['pwd']}")
return param
data = [
{"username": "name1", "pwd": "pwd1"},
{"username": "name2", "pwd": "pwd2"},
]
#如果需要传多个参数,需要通过字典去传
@pytest.mark.parametrize("pre_data", data, indirect=True)
def test_name_pwd(pre_data):
print(f"账号是:{pre_data['username']},密码是:{pre_data['pwd']}")
多个fixture(只加一个装饰器)
# 多个fixture
@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
data = [
("name1", "pwd1"),
("name2", "pwd2")
]
@pytest.mark.parametrize("input_user,input_psw", data, indirect=True)
def test_more_fixture(input_user, input_psw):
print("fixture返回的内容:", input_user, input_psw)
多个fixture(叠加装饰器)
# 多个fixture
@pytest.fixture(scope="function")
def input_user(request):
user = request.param
print("登录账户:%s" % user)
return user
@pytest.fixture(scope="function")
def input_psw(request):
psw = request.param
print("登录密码:%s" % psw)
return psw
name = ["name1", "name2"]
pwd = ["pwd1", "pwd2"]
@pytest.mark.parametrize("input_user", name, indirect=True)
@pytest.mark.parametrize("input_psw", pwd, indirect=True)
def test_more_fixture(input_user, input_psw):
print("fixture返回的内容:", input_user, input_psw)
跳过执行测试用例,有可选参数 reason:跳过的原因,会在执行结果中打印
import pytest
@pytest.fixture(autouse=True)
def login():
print("====登录====")
def test_case01():
print("我是测试用例11111")
@pytest.mark.skip(reason="不执行该用例!!因为没写好!!")
def test_case02():
print("我是测试用例22222")
class Test1:
def test_1(self):
print("%% 我是类测试用例1111 %%")
@pytest.mark.skip(reason="不想执行")
def test_2(self):
print("%% 我是类测试用例2222 %%")
@pytest.mark.skip(reason="类也可以跳过不执行")
class TestSkip:
def test_1(self):
print("%% 不会执行 %%")
作用:在测试用例执行期间强制跳过不再执行剩余内容
类似:在Python的循环里面,满足某些条件则break 跳出循环
def test_function():
n = 1
while True:
print(f"这是我第{n}条用例")
n += 1
if n == 5:
pytest.skip("我跑五次了不跑了")
当 allow_module_level=True 时,可以设置在模块级别跳过整个模块
import sys
import pytest
if sys.platform.startswith("win"):
pytest.skip("skipping windows-only tests", allow_module_level=True)
@pytest.fixture(autouse=True)
def login():
print("====登录====")
def test_case01():
print("我是测试用例11111")
方法:
skipif(condition, reason=None)
参数:
condition:跳过的条件,必传参数
reason:标注原因,必传参数
使用方法:
@pytest.mark.skipif(condition, reason=“xxx”)
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
@pytest.mark.skipif(condition=2>1,reason = "跳过该函数") # 跳过测试函数test_b
def test_b(self):
print("------->test_b")
assert 0
执行结果:
test_abc.py
------->setup_class
------->test_a #只执行了函数test_a
.
------->teardown_class
s # 跳过函数
# 标记
skipmark = pytest.mark.skip(reason="不能在window上运行=====")
skipifmark = pytest.mark.skipif(sys.platform == 'win32', reason="不能在window上运行啦啦啦=====")
@skipmark
class TestSkip_Mark(object):
@skipifmark
def test_function(self):
print("测试标记")
def test_def(self):
print("测试标记")
@skipmark
def test_skip():
print("测试标记")
作用:如果缺少某些导入,则跳过模块中的所有测试
参数列表
pexpect = pytest.importorskip("pexpect", minversion="0.3")
@pexpect
def test_import():
print("test")
前言
import pytest
@pytest.mark.model
def test_model_a():
print("执行test_model_a")
@pytest.mark.regular
def test_regular_a():
print("test_regular_a")
@pytest.mark.model
def test_model_b():
print("test_model_b")
@pytest.mark.regular
class TestClass:
def test_method(self):
print("test_method")
def testnoMark():
print("testnoMark")
命令运行
pytest -s -m model test_one.py
如何避免warnings
[pytest]
markers =
model: this is model mark
如果不想标记 model 的用例
pytest -s -m " not model" test_one.py
如果想执行多个自定义标记的用例
pytest -s -m “model or regular” 08_mark.py
pytest允许在多个级别启用测试化参数:
1)pytest.fixture()允许fixture有参数化功能
2)pytest.mark.parametrize 允许在测试函数和类中定义多组参数和fixtures
3)pytest_generate_tests允许定义自定义参数化方案或扩展
def parametrize(self,argnames, argvalues, indirect=False, ids=None, scope=None):
argnames:
含义:参数值列表
格式:字符串"arg1,arg2,arg3"
例如:
@pytest.mark.parametrize(“name,pwd”, [(“yy1”, “123”), (“yy2”, “123”)])
argvalues:
含义:参数值列表
格式:必须是列表,如:[ val1,val2,val3 ]
如果只有一个参数,里面则是值的列表如:@pytest.mark.parametrize(“username”, [“yy”, “yy2”, “yy3”])
如果有多个参数例,则需要用元组来存放值,一个元组对应一组参数的值,如:@pytest.mark.parametrize(“name,pwd”, [(“yy1”, “123”), (“yy2”, “123”), (“yy3”, “123”)])
ids:
含义:用例的id
格式:传一个字符串列表
作用:可以标识每一个测试用例,自定义测试数据结果的显示,为了增加可读性
indirect:
作用:如果设置成 True,则把传进来的参数当函数执行,而不是一个参数(下一篇文章即讲解
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
print(f"测试数据{test_input},期望结果{expected}")
assert eval(test_input) == expected
方便测试函数对测试属于的获取。
方法:
parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
常用参数:
argnames:参数名
argvalues:参数对应值,类型必须为list
当参数为一个时格式:[value]
当参数个数大于一个时,格式为:[(param_value1,param_value2…),(param_value1,param_value2…)]
使用方法:
@pytest.mark.parametrize(argnames,argvalues)
️ 参数值为N个,测试方法就会运行N次
单个参数
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
@pytest.mark.parametrize("a",[3,6]) # a参数被赋予两个值,函数会运行两遍
def test_a(self,a): # 参数必须和parametrize里面的参数一致
print("test data:a=%d"%a)
assert a%3 == 0
执行结果:
test_abc.py
------->setup_class
test data:a=3 # 运行第一次取值a=3
.
test data:a=6 # 运行第二次取值a=6
.
------->teardown_class
多个参数
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
@pytest.mark.parametrize("a,b",[(1,2),(0,3)]) # 参数a,b均被赋予两个值,函数会运行两遍
def test_a(self,a,b): # 参数必须和parametrize里面的参数一致
print("test data:a=%d,b=%d"%(a,b))
assert a+b == 3
执行结果:
test_abc.py
------->setup_class
test data:a=1,b=2 # 运行第一次取值 a=1,b=2
.
test data:a=0,b=3 # 运行第二次取值 a=0,b=3
.
------->teardown_class
函数返回值作为参数
import pytest
def return_test_data():
return [(1,2),(0,3)]
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
@pytest.mark.parametrize("a,b",return_test_data()) # 使用函数返回值的形式传入参数值
def test_a(self,a,b):
print("test data:a=%d,b=%d"%(a,b))
assert a+b == 3
执行结果:
test_abc.py
------->setup_class
test data:a=1,b=2 # 运行第一次取值 a=1,b=2
.
test data:a=0,b=3 # 运行第二次取值 a=0,b=3
.
------->teardown_class
“笛卡尔积”,多个参数化装饰器
# 笛卡尔积,组合数据
data_1 = [1, 2, 3]
data_2 = ['a', 'b']
@pytest.mark.parametrize('a', data_1)
@pytest.mark.parametrize('b', data_2)
def test_parametrize_1(a, b):
print(f'笛卡尔积 测试数据为 : {a},{b}')
参数化,标记数据
# 标记参数化
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
pytest.param("6 * 9", 42, marks=pytest.mark.xfail),
pytest.param("6*6", 42, marks=pytest.mark.skip)
])
def test_mark(test_input, expected):
assert eval(test_input) == expected
标记测试函数为失败函数
方法:
xfail(condition=None, reason=None, raises=None, run=True, strict=False)
常用参数:
condition:预期失败的条件,必传参数
reason:失败的原因,必传参数
使用方法:
@pytest.mark.xfail(condition, reason="xx")
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
@pytest.mark.xfail(2 > 1, reason="标注为预期失败") # 标记为预期失败函数test_b
def test_b(self):
print("------->test_b")
assert 0
执行结果:
test_abc.py
------->setup_class
------->test_a
.
------->test_b
------->teardown_class
x # 失败标记
需安装第三方插件:pytest-rerun、pytest-rerunfailures
if __name__=="__main__":
pytest.main(['-s','test_firstFile.py']) # 第一次运行,如果有失败的用例/第一次没有失败的用例
pytest.main(['-s','--lf','test_firstFile.py']) # 收集到第一次失败的用例,进行执行/则运行全部
注意:如果用例数较多,第一次运行全部成功的情况,第二个pytest.main(),是会收集所有的用例再执行一遍;建议使用失败重试次数(–reruns=1),失败一次后,立刻执行一次,也可减少用例的失败率
失败重试方式:
1、可在命令行 –reruns=1 reruns_delay=2 失败后重运行1次,延时2s
2、使用装饰器进行失败重运行
@pytest.mark.flaky(reruns=1, reruns_delay=2)
使用方式:
命令行参数:–reruns n(重新运行次数),–reruns-delay m(等待运行秒数)
装饰器参数:reruns=n(重新运行次数),reruns_delay=m(等待运行秒数)
重新运行所有失败的用例:
#运行失败的 fixture 或 setup_class 也将重新执行
pytest --reruns=5
添加重新运行的延时:
#要在两次重试之间增加延迟时间,使用 --reruns-delay 命令行选项,指定下次测试重新开始之前等待的秒数
pytest.main( [‘-vs’,‘–reruns=5’,‘–reruns_delay=10’,‘./testcase/test_debug.py’,‘–report=_report.html’])
重新运行指定的测试用例:
#要指定某些测试用例时,需要添加 flaky 装饰器@pytest.mark.flaky(reruns=5) ,它会在测试失败时自动重新运行,且需要指定最大重新运行的次数
import pytest
@pytest.mark.flaky(reruns=5)
def test_example():
import random
assert random.choice([True, False, False])
#同样的,这个也可以指定重新运行的等待时间
@pytest.mark.flaky(reruns=5, reruns_delay=2)
def test_example():
import random
assert random.choice([True, False, False])
注意:
1.如果指定了用例的重新运行次数,在命令行添加的 --reruns 对这些用例是不会生效的
2.不可以和 fixture 装饰器@pytest.fixture()一起使用
3.该插件与 pytest-xdist 的 --looponfail 标志不兼容
4.该插件与核心 --pdb 标志不兼容
安装插件:
pip3 install pytest-html
执行方式:
#会在当前目录下创建一个 report.html 的测试报告
pytest --html=report.html
合并CSS:
#上面命令生成的报告,css 是独立的,分享报告的时候样式会丢失,为了更好的分享发邮件展示报告,可以把css样式合并到html里
pytest --html=report.html --self-contained-html
下载插件:pip3 install pytest-testreport
pytest -vs --reruns 5 ./testcase/test_debug.py --report=_report.html