前提
pytest是一个非官方的单元测试框架,需要先进行安装。所以pip一下
技术点
一、运行参数(进入到相应目录)
1、无参数运行
运行目录下的所有py文件:pytest
运行目录下某一个py文件:pytest test_01.py
运行目录下py文件中的某个类:pytest test_02.py::TestClass
运行目录下py文件中某个类的某个方法:pytest test_02.py::TestClass::test_one
指定目录运行:pytest testpy
2、-v参数
打印详细的日志信息
3、-s参数
代码里面有 print 输出语句,想在运行结果中打印 print 输出
4、-k参数
pytest -k '类名' //运行指定的类
pytest -k '方法名' //运行指定的方法
pytest -k '类名 and not 方法名' //运行类里的方法,不包含某个方法
5、-x参数
遇到失败用例立即停止运行
6、--maxfail参数
用例失败个数达到阀值后停止运行
pytest --maxfail 2 test_02.py
7、-m参数
只运行标记 @pytest.mark.[标记名]的方法和类
比如类名上添加:
@pytest.mark.oneone,
执行命令:pytest -m "oneone" test_02.py
如果有多个标记可以用 and 或 or 进行组合
8、--durations参数
获取执行最慢的一个:pytest --durations=1
9、--collect-only参数
只收集用例不执行,可用于统计用例数
二、pytest框架结构
见test_01.py
1 import pytest 2 3 def setup_module(): 4 print("\nsetup_module,只执行一次,当有多个测试类的时候使用") 5 def teardown_module(): 6 print("\nteardown_module,只执行一次,当有多个测试类的时候使用") 7 8 9 # setup_function teardown_function作用于类外的函数 10 def setup_function(): 11 print("\nsetup_function") 12 def teardown_function(): 13 print("\nsetup_function") 14 15 def test_cls_out(): 16 print("类外的函数方法") 17 18 19 class TestPytest1: 20 def setup_class(self): 21 print("\nsetup_class1,只执行一次,当有多个测试方法的时候使用") 22 def teardown_class(self): 23 print("\nteardown_class1,只执行一次,当有多个测试方法的时候使用") 24 25 26 def setup_method(self): 27 print("\nsetup_method1,每个测试方法都执行一次") 28 def teardown_method(self): 29 print("teardown_method1,每个测试方法都执行一次") 30 31 32 def setup(self): 33 print("setup") 34 def teardown(self): 35 print("teardown") 36 37 38 def test_three(self): 39 print("test_three,测试用例") 40 41 def test_four(self): 42 print("test_four,测试用例")
三、控制执行顺序
安装:pip install pytest-ordering
负数越小越先执行(-100,-18,-1)
正数越小越先执行(1,18,100)
1 import pytest 2 3 4 class Testpy: 5 @pytest.mark.run(order=1) 6 def test_one(self): 7 print(111) 8 9 @pytest.mark.run(order=18) 10 def test_two(self): 11 print(222) 12 13 @pytest.mark.run(order=100) 14 def test_three(self): 15 print(333)
四、并发执行
安装:pip install pytest-xdist
多个CPU并行执行用例,如果参数为 auto 自动检测系统的 CPU 数目;如果参数为数字,则指定运行测试的处理器进程数。
pytest -n auto
pytest -n [num]
五、pytest-html 生成测试报告
安装:pip install pytest-html
指定报告的存放路径
--html=./report/report.html
加这个参数生成的报告css不是独立的
--self-contained-html
六、assert断言
assert a
assert a == b
assert a in b
assert not a
assert a != b (a <> b一般不再使用)
七、@pytest.fixture
fixture 有一个参数 scope,通过 scope 可以控制 fixture 的作用范围,根据作用范围大小划分:session> module> class> function
具体作用范围如下:
function 函数或者方法级别都会被调用(默认)
class 类级别调用一次
module 模块级别调用一次
session 是多个文件调用一次(可以跨.py文件调用,每个.py文件就是module)
1、以参数的形式传入到方法里执行
1 import pytest 2 3 @pytest.fixture() 4 def login(): 5 print("登录需要的操作步骤") 6 7 @pytest.fixture() 8 def operate(): 9 print("用例的执行步骤") 10 11 @pytest.fixture() 12 def xiao(): 13 print("1234567890") 14 15 # 需要在函数中传入函数名,函数上需要先标记上 @pytest.fixture() 16 def test_case1(login, operate, xiao): 17 print("test_case1,需要登录执行完毕")
2、指定范围内共享
1 import pytest 2 3 4 @pytest.fixture(scope="module") 5 def open(): 6 print("打开浏览器") 7 yield 8 9 print("执行teardown !") 10 print("最后关闭浏览器") 11 12 13 # 方式一 14 # raise返回异常,不影响open中yield后续的操作 15 # 如果没有异常可以不写raise,抛出异常 16 @pytest.mark.usefixtures("open") 17 def test_search1(): 18 print("test_search1") 19 # raise NameError 20 pass 21 22 23 # 方式二 24 # raise返回异常,不影响open中yield后续的操作 25 # 如果没有异常可以不写raise,抛出异常 26 def test_search2(open): 27 print("test_search2") 28 # raise NameError 29 pass
3、conftest.py应用
conftest.py等同于scope=session
pytest test_scope1.py test_scope2.py
4、自动执行fixture装饰下的函数
见test_04.py
5、fixture传递参数
1 import pytest 2 3 @pytest.fixture(params=[1, 2, 3]) 4 def params(request): 5 return request.param 6 7 def test_com(params): 8 print(f"测试数据:{params}") 9 assert params < 5
八、@pytest.mark.parametrize参数化
传一个参数
1 # 传一个参数 2 @pytest.mark.parametrize("user", ["13552977251", "13552554252"]) 3 def test_user(user): 4 print(user)
传两个参数
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+5", 7), ("7*5", 35)])
如上 "test_input,expected" 可以修改为 列表或者元组 的形式
列表:@pytest.mark.parametrize(["test_input","expected"], [("3+5", 8), ("2+5", 7), ("7*5", 35)])
元组:@pytest.mark.parametrize(("test_input","expected"), [("3+5", 8), ("2+5", 7), ("7*5", 35)])
1 # 传两个参数 2 @pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+5", 7), ("7*5", 35)]) 3 def test_function(test_input, expected): 4 # eval可以将字符串转成3+5的表达方式 5 print(test_input, expected) 6 assert eval(test_input) == expected
传三个参数 三组数据,ids是别名必须与参数的数量保持一致
参数组合
1 # 参数组合 2 @pytest.mark.parametrize("x", [1, 2]) 3 @pytest.mark.parametrize("y", [3, 4, 5]) 4 def test_num(x, y): 5 # print(f"测试数据组合x: {x} , y:{y}") 6 print("测试数据组合x:"+str(x)+" y:"+str(y))
函数返回值类型
1 # 函数返回值类型 2 def return_data(): 3 return [(1, 2), (3, 4)] 4 5 @pytest.mark.parametrize("a,b", return_data()) 6 def test_data(a, b): 7 print(a) 8 print(b)
用yaml文件做为参数化的数据源
companyid.yaml
- - 23725503 - 24721214 - 2352987806
data.yaml
- - 20 - 30 - - 40 - 50
脚本:
1 import pytest 2 import yaml 3 4 5 @pytest.mark.parametrize('a, b', yaml.safe_load(open("data.yaml", encoding='utf-8'))) 6 # @allure.step("方法的描述信息") 7 def test_fo(a, b): 8 print(a) 9 print(b) 10 11 12 @pytest.mark.parametrize('company_id', yaml.safe_load(open("companyid.yaml", encoding='utf-8'))) 13 # @allure.step("方法的描述信息") 14 def test_foo(company_id): 15 print("企业ID:", company_id)