Python单元测试框架pytest

前提

pytest是一个非官方的单元测试框架,需要先进行安装。所以pip一下

技术点

一、运行参数(进入到相应目录)

1、无参数运行

pytest运行命名规则:运行时查找当前目录下及子目录下的以 test_*.py 或者 *_test.py为标识的文件,文件中的内容只运行test开头的函数或类

运行目录下的所有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 输出

-v 和-s 可以写在一块 -sv

4、-k参数

pytest -k '类名' //运行指定的类
pytest -k '方法名' //运行指定的方法
pytest -k '类名 and not 方法名' //运行类里的方法,不包含某个方法

pytest -k “test_demo1 or test_demo2” //执行test_demo1 和 test_demo2

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 进行组合

 1 import pytest
 2 
 3 def inc(x):
 4     return x + 1
 5 
 6 class TestPytest:
 7     def setup(self):
 8         print("setup")
 9 
10     @pytest.mark.success
11     def test_demo_1(self):
12         assert inc(4) == 5
13 
14     @pytest.mark.fail
15     def test_demo_2(self):
16         assert inc(3) == 5
17 
18     def teardown(self):
19         print("teardown")
1 仅执行标记为success的用例,以下两种写法均可以:
2 pytest -m "success and not fail" test_pytest.py 
3 pytest -m "success" test_pytest.py 
8、--durations参数

获取执行最慢的一个:pytest --durations=1

9、--collect-only参数

只收集用例不执行,可用于统计用例数

10、-q参数

只显示用例执行结果

 

二、pytest前置和后置

 1 import pytest
 2 
 3 def setup_module():
 4     print("\nsetup_module,一个py文件只执行一次,不管该py文件中有多少方法和类")
 5 def teardown_module():
 6     print("\nteardown_module,一个py文件只执行一次,不管该py文件中有多少方法和类")
 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   # 等同于下边的setup、teardown
26     def setup_method(self):
27         print("\nsetup_method1,在类中的每个测试方法都执行一次")
28     def teardown_method(self):
29         print("teardown_method1,在类中的每个测试方法都执行一次")
30 
31   # 等同于上边的setup_method、teardown_method
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]

分布式执行用例的设计原则:
1、用例之间是独立的,用例之间没有依赖关系,用例可以完全独立运行
2、用例执行没有顺序,随机都可以正常执行
3、每个用例都能重复运行,运行结果不会影响其他用例

五、pytest-html 生成测试报告

安装:pip install pytest-html
指定报告的存放路径
--html=./report/report.html
 加这个参数生成的报告css不是独立的
 --self-contained-html

#test_pytest.py
1
import pytest 2 3 def inc(x): 4 return x + 1 5 6 class TestPytest: 7 def setup(self): 8 print("setup") 9 10 @pytest.mark.parametrize("data,expected", [(2, 3), (6, 7), (8, 10)]) 11 def test_demo_1(self, data, expected): 12 assert inc(data) == expected 13 14 def teardown(self): 15 print("teardown")

终端执行命令:pytest test_pytest.py --html=report.html --self-contained-html

会在当前目录下生成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 每个.py文件【模块级别】调用一次
  session 多个文件调用一次(可以跨.py文件调用,每个.py文件就是module)

1、以参数的形式传入到方法里执行

import pytest

@pytest.fixture()
def login():
    text = "登录需要的操作步骤"
    return text

@pytest.fixture()
def operate():
    text = "用例的执行步骤"
    return text

@pytest.fixture()
def xiao():
    text = "1234567890"
    return text

# 需要在函数中传入函数名,函数上需要先标记上 @pytest.fixture()
def test_case1(login, operate, xiao):  #在入参中 传入方法名 即可实现调用已装饰的fixture下函数
    print(f"需要先进行登录:{login},然后执行用例:{operate},最后执行:{xiao}")
    print("test_case1,需要登录执行完毕")

2、fixture的两种引用方式

 1 import pytest
 2 
 3
 4 @pytest.fixture()
 5 def open():
 6     print("打开浏览器")
 7     yield
 8 
 9     print("执行teardown !")
10     print("最后关闭浏览器")
11 
12 
13 # 方式一:装饰器方式引用
16 @pytest.mark.usefixtures("open")
17 def test_search1():
18     print("test_search1")
19     # raise NameError
20     pass
21 
22 
23 # 方式二:在函数中传入
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

相应代码见:https://github.com/hanshoukai/pytest_fixture

fixture为function

fixture为class

 

 fixture为module

  fixture为session

yield及调用执行流程

  •  yield前的代码为前置执行逻辑
  •  yield为分界线,后面的代码为用例结束后执行的代码逻辑
  •  调用方式都可以直接将方法名open当做参数传入
  •  调用执行流程:运行测试用例--发现参数为方法名--寻找同名的函数方法--先执行同名函数方法--碰到yield--执行测试用例--执行yield后续代码
  • conftest.py无异常,测试用例发生异常时,不会影响到yield的执行,会在异常结束后继续执行yield后的代码
  • conftest.py  yield前存在异常,测试用例调用后续yield的代码,不会再继续执行

 

4、自动执行fixture装饰下的函数
conftest.py中标记为:@pytest.fixture(autouse="true")

每个测试函数都会自动调用该前置函数


5、fixture传递参数

import pytest

@pytest.fixture(params=[1, 2, 3])
# 函数需要传入request【固定写法】
def params(request):
    # 通过request.param访问每个参数的值
    return request.param

def test_com(params):
    print(f"测试数据:{params}")
    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)  # 【20 - 30】
 9     print(b)  # 【40 - 50】
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)  # 企业ID: [23725503, 24721214, 2352987806]

九、pytest.ini模板

 1 [pytest]
 2 # 空格分隔,可添加多个命令行参数  重试两次每隔5秒  生成报告
 3 addopts = -sv --reruns 2 --reruns-delay 5 --html=./report/report.html --self-contained-html
 4 #addopts = -s -v --alluredir=./result
 5 # 当前目录下
 6 testpaths = ./testcase/
 7 #配置测试搜索的文件名称,当前目录下以test开头,以.py结尾的所有文件
 8 python_files = test*.py
 9 #配置测试搜索的测试类名,当前目录下以Test开头的所有类
10 python_classes = Test
11 #配置测试搜索的测试类名,当前目录下以test_开头的所有方法
12 python_functions = test_*

十、失败重试

用例失败后自动重新运行:pip install pytest-rerunfailures

使用方法:pytest test_x.py --reruns=2 --reruns-delay 5  #失败后重试2次,每次间隔5秒

在脚本中指定定义重跑的次数,这个时候在运行的时候,就无需加上 --reruns 这个参数

1 @pytest.mark.flaky(reruns=6, reruns_delay=2)
2 def test_example(self):
3     print(3)

 十一、多重校验

单个测试用例中执行多个断言,并查看所有的失败信息,从而更好地理解测试结果

安装:pip install pytest-assume

import pytest

def test_simple_assume():
    pytest.assume(1==1)
    pytest.assume(1!=2)
    pytest.assume(1!=3)

 

posted @ 2021-10-28 09:28  韩凯1202  阅读(369)  评论(0编辑  收藏  举报