前提

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

单元测试框架pytest_测试数据

 

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是别名必须与参数的数量保持一致

单元测试框架pytest_测试方法_02

参数组合

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)