pytest测试框架01
1、pytest测试用例及框架结构
(1)用例结构
def test_XXX(self):
#测试步骤1
#测试步骤2
#断言 实际结果 对比 预期结果
assert ActualResult == ExpectedResult # 断言
(2)框架结构setup/teardown
①setup_module/teardown_module:全局模块级
②setup_class/teardown_class:类级,只在类中前后运行一次
③setup_function/teardown_function:函数级,在类外
④setup_method/teardown_method:方法级,类中的每个方法执行前后
⑤setup/teardown:在类中,运行在调用方法的前后
#模块级别,只被调用一次
def setup_module():
print("资源准备:setup module")
def teardown_module():
print("资源销毁:teardown module")
def test_case1():
print("case1")
def test_case2():
print("case2")
#方法级别
def setup_function():
print("资源准备:setup function")
def teardown_function():
print("资源销毁:teardown function")
#类级别
class TestDemo:
#执行类 前后分别执行setup_class ,teardown_class
def setup_class(self):
print("TestDemo setup_class")
def teardown_class(self):
print("TestDemo teardown_class")
#每个类里面的方法,前后执行setup ,teardown
def setup(self):
print("TestDemo setup")
def teardown(self):
print("TestDemo teardown")
def test_demo1(self):
print("test demo1")
def test_demo2(self):
print("test demo2")
2、pytest参数化用例
(1)参数化
①通过参数的方式传递数据,从而实现数据和脚本分离。
②并且可以实现用例的重复生成与执行。
(2)参数化实现方法
装饰器:@pytest.mark.parametrize
1)单参数,可以将数据放在列表中
#单参数,参数化实现用例的动态生成
search_list01 = ["appium", "selenium", "pytest"]
@pytest.mark.parametrize("search_key",
["appium", "selenium", "pytest", "", "requests", "abc"])
def test_search01(search_key):
assert search_key in search_list01 # 断言
2)多参数情况:
①将数据放在列表嵌套元组中
②将数据放在列表嵌套列表中
#多参数,参数化实现用例的动态生成
#列表嵌套元组
@pytest.mark.parametrize("username, password",
[("right username", "right password"),
("wrong username", "wrong password")])
def test_login(username, password):
print(f"登录的用户名:{username},密码:{password}")
#列表嵌套列表
@pytest.mark.parametrize("username, password", [
["right username", "right password"],
["wrong username", "wrong password"]])
def test_login(username, password):
print(f"登录的用户名:{username},密码:{password}")
3)用例重命名-添加 ids 参数
①通过ids参数,将别名放在列表中
#用例重命名-添加ids 参数,为用例起个别名,ids列表参数的个数要与参数值的个数一致
@pytest.mark.parametrize("username, password",
[["right username", "right password"],
["wrong username", "wrong password"],
["", "password"]],
ids=["right username and password",
"wrong username and password",
"username is null"])
def test_login(username, password):
print(f"登录的用户名:{username},密码:{password}")
②用例重命名-添加 ids 参数(中文)
#第一步,创建conftest.py 文件 ,将下面内容添加进去,运行脚本
def pytest_collection_modifyitems(items):
"""
测试用例收集完成时,将收集到的用例名name和用例标识nodeid的中文信息显示在控 制台上
"""
for i in items:
i.name=i.name.encode("utf-8").decode("unicode_escape")
i._nodeid=i.nodeid.encode("utf-8").decode("unicode_escape")
#用例重命名- ids 支持中文
@pytest.mark.parametrize("username, password",
[["right username", "right password"],
["wrong username", "wrong password"],
["", "password"]],
ids=["正确的用户名和密码",
"错误的用户名和密码",
"用户名为空"])
def test_login(username, password):
print(f"登录的用户名:{username},密码:{password}")
4)笛卡尔积
#笛卡尔积,装饰器优先选择离方法最近的,由近及远
@pytest.mark.parametrize("b", ["a", "b", "c"])
@pytest.mark.parametrize("a", [1, 2, 3])
def test_param1(a, b):
print(f"笛卡积形式的参数化中 a={a} , b={b}")
3、pytest标记测试用例
1)场景:只执行符合要求的某一部分用例 可以把一个web项目划分多个模块,然后指定模块名称执行。
2)解决: 在测试用例方法上加 @pytest.mark.标签名
3)执行: -m 执行自定义标记的相关用例
import pytest
def double(a):
return a * 2
#测试数据,整型
@pytest.mark.int
def test_double_int():
print("test double int")
assert 2 == double(1)
#测试数据,负数
@pytest.mark.minus
def test_double_minus():
print("test double minus")
assert -2 == double(-1)
#测试数据,浮点型
@pytest.mark.float
def test_double1_float():
print("test double float")
assert 0.2 == double(0.1)
@pytest.mark.float
def test_double2_minus():
print("test double float")
assert -0.2 == double(-0.1)
@pytest.mark.zero
def test_double_0():
print("test double zero")
assert 0 == double(0)
@pytest.mark.bignum
def test_double_bignum():
print("test double bignum")
assert 200 == double(100)
#测试数据,字符串
@pytest.mark.str
def test_double_str1():
print("test double str")
assert 'aa' == double('a')
@pytest.mark.str
def test_double_str2():
print("test double str")
assert 'a$a$' == double('a$')
4)Terminal执行命令行,只执行字符串str,-vs打印日志:
pytest_test % pytest test_003.py -vs -m "str"
5)出现warnings警告提示,不影响运行,是因为给测试用例加上了标签的名字,pytest无法识别这些标签名字,pytest有内置的标签
解决:新建一个pytest.ini文件,将新标签名字添加进去,不要写在一行,会无法识别,换行也不要放在顶头,会认为一个key
[pytest]
markers = str
bignum
minus
int
zero
float
4、pytest设置跳过Skip、预期失败用例xFail
1)这是 pytest 的内置标签,可以处理一些特殊的测试用例,不能成功的测试用例
2)skip - 始终跳过该测试用例
3)skipif - 遇到特定情况跳过该测试用例
4)xfail - 遇到特定情况,产生一个“期望失败”输出
①添加装饰器
@pytest.mark.skip
@pytest.mark.skipif
import pytest
#添加装饰器
@pytest.mark.skip
def test_aaa():
print("暂未开发")
assert True
#添加跳过原因
@pytest.mark.skip(reason="暂未实现")
def test_b():
print("暂未开发")
assert True
②代码中添加跳过代码:pytest.skip(reason)
#代码中添加跳过代码,pytest.skip(reason=)
def check_login():
return True
def test_function():
print("start")
# 如果未登录,则跳过后续步骤
if not check_login():
pytest.skip(reason="unsupported configuration")
print("end")
③预期结果为 fail ,标记用例为 fail,通过为XPASS,不通过为XFAIL。用法:添加装饰器@pytest.mark.xfail
#预期结果为 fail ,标记用例为 fail
@pytest.mark.xfail # 标记后依然会被执行,起到提示左右
def test_a():
print("xfail 方法执行")
assert 2 == 2
@pytest.mark.xfail
def test_b():
print("xfail 方法执行")
assert 1 == 2
#添加原因说明
xfail = pytest.mark.xfail
@xfail(reason="bug")
def test_b():
print("xfail 方法执行")
assert 2 == 2
5、pytest测试用例调度与运行
1)执行包下所有的用例:pytest/py.test [包名]
2)执行单独一个 pytest 模块:pytest 文件名.py
3)运行某个模块里面某个类:pytest 文件名.py::类名
4)运行某个模块里面某个类里面的方法:pytest 文件名.py::类名::方法名
5)命令行参数-使用缓存状态
①--lf(--last-failed) 只重新运行故障。
②--ff(--failed-first) 先运行故障然后再运行其余的测试
6、pytest命令行常用参数
①—help
②-x 用例一旦失败(fail/error),就立刻停止执行
③--maxfail=num 失败用例达到num,就停止执行
④-m 标记用例
⑤-k 执行包含某个关键字的测试用例,如-k “str”
⑥-v 打印详细日志
⑦-s 打印输出日志(一般-vs一块儿使用)
⑧—collect-only(测试平台,pytest 自动导入功能 )只收集用例,不运行
7、python执行pytest
(1)使用 main 函数
python的机制,__name__等于__main__;print(__name__),输出结果为__main__
if __name__ == '__main__':
# 1、运行当前目录下所有符合规则的用例,包括子目录(test_*.py 和 *_test. py)
pytest.main()
# 2、运行test_mark1.py::test_dkej模块中的某一条用例
pytest.main(['test_002.py::test_case1', '-vs'])
# 3、运行某个 标签
pytest.main(['test_003.py', '-vs', '-m', 'str'])
(2)使用 python -m pytest 调用 pytest(jenkins 持续集成用到)
8、pytest异常处理
(1)异常处理方法 try …except
try:
a = int(input("输入被除数:"))
b = int(input("输入除数:"))
c = a / b
print("您输入的两个数相除的结果是:", c)
except(ValueError, ArithmeticError):
print("程序发生了数字格式异常、算术异常之一")
except:
print("未知异常")
print("程序继续运行")
(2)异常处理方法 pytest.raise()
①可以捕获特定的异常
②获取捕获的异常的细节(异常类型,异常信息)
③发生异常,后面的代码将不会被执行
import pytest
def test_raise():
with pytest.raises(ValueError, match='must be 0 or None'):
raise ValueError("value must be 0 or None")
def test_raise1():
# ValueError为预期异常
with pytest.raises(ValueError) as exc_info:
# 抛出的异常ZeroDivisionError
raise ZeroDivisionError("value must be 42")
assert exc_info.type is ValueError
assert exc_info.value.args[0] == "value must be 42"