【测试】Pytest框架

it2024-01-09  69

pytest目录

1、pytest基础示例2、断言2.1断言用法:2.2`pytest.raises()`抛异常的使用2.3 `pytest.warns()`抛警告的使用 3、setup和teardown函数4、fixture基本使用4.1基础4.2conftest.py文件 5、fixture参数6、pytest.mark标记7、pytest配置文件8、常用插件8.1、生成测试报告8.2、控制函数执行顺序8.3、失败重试8.4按名称取消插件 9、yaml9.1、理论9.2、使用python处理yaml文件 安装依赖包:

pip install -U pytest -i https://pypi.tuna.tsinghua.edu.cn/simple

特点:

是一个命令行工具,编写用例简单,可读性强 非常容易上手,入门简单,文档丰富,文档中有很多实例可以参考 支持单元测试和功能测试 支持参数化 执行测试过程中可以将某些测试跳过,或者对某些预期失败的Case标记成失败 支持重复执行失败的Case 支持运行由unittest编写的测试Case 具有很多第三方插件,并且可以自定义扩展 方便的和持续集成工具集成 可以运行以test开头或test结尾的包、文件和方法 可以使用assert进行断言

1、pytest基础示例

pytest 默认能够自动识别 test 开头函数和方法,以及 Test 开头的类。,其他的不会自动识别。

测试代码示例:

import pytest def test_one(): print('1111') def test_two(): print('22222') class TestTask(object): def test_1(self): print('test 11111111') def test_2(self): print('test 222222222222') if __name__ == '__main__': pytest.main(['-s']) pytest -s # 运行当前目录所有test开头的文件 pytest -s xxx.py # 运行指定的文件 # 代码 pytest.main(['-s']) pytest.main(['-s', 'xxx.py'])

终端测试示例代码:pytest test_1.py -s

====================== test session starts ===================== platform linux -- Python 3.5.2, pytest-6.1.1, py-1.9.0, pluggy-0.13.1 rootdir: /home/python/Desktop/code/demo plugins: celery-4.4.5 collected 4 items test_1.py 1111 .22222 .test 11111111 .test 222222222222 . ======================= 4 passed in 0.02s ======================== 命令如果不加-s不会显示代码中的标准输出(print、logging),示例代码最后一句不加-s也是一个道理。

2、断言

2.1断言用法:

import pytest def test_1(): num = 1 + 1 # assert 条件, '产生异常的提示信息' assert num == 3, '实际:num=2,预期:num==3;条件不满足,所以断言失败,打印本条信息' if __name__ == '__main__': pytest.main()

终端测试:

2.2pytest.raises()抛异常的使用

预测到参数不符合要求,会抛出TypeError异常;若出现该异常,则测试用例执行通过

用法: # with pytest.raises(预期的异常类型): # 实际产生的异常 # 如果实际产生的异常和预期的异常类型一样,测试通过 import pytest def foo(a): """测试函数""" if a == 1: raise TypeError("异常a=11111111") elif a == 2: raise ValueError("异常a=22222222") elif a == 3: raise ValueError("异常a=33333333") class TestAssert(object): def test_exception_typeerror(self): with pytest.raises(TypeError): # 传入a=1, 引发测试函数错误 foo(1)

把异常信息保存到一个变量err_info中 def test_exception_typeerror(self): # with pytest.raises(预期的异常类型) as 异常对象: # 实际产生的异常 # 如果实际产生的异常和预期的异常类型一样,测试通过 with pytest.raises(TypeError) as err_info: foo(1) # 产生TypeError,保存到err_info if err_info.type == TypeError: print('err_info保存了异常信息') 通过异常的内容捕捉异常match = '' import pytest def foo(a): """测试函数""" if a == 1: raise TypeError("异常1111111") elif a == 2: raise ValueError("异常22222222") else: raise ValueError("异常33333333") def test_3(): with pytest.raises(ValueError, match="异常33333333") as err_info: foo(2) # 触发ValueError,同时异常内容为match if err_info.type == ValueError: print(err_info, 'err_info保存了异常信息') if __name__ == '__main__': pytest.main(['-s'])

2.3 pytest.warns()抛警告的使用

注意:

警告不会终止程序运行,异常会终止程序运行

import warnings import pytest def test_1(): # with pytest.warns(预期的警告类型): # 实际产生的警告 # 如果实际产生的警告和预期的警告类型一样,测试通过 with pytest.warns(RuntimeWarning): warnings.warn("my warning", RuntimeWarning) def test_2(): # 警告断言,可以同时断言多个(异常不可以),保存在一个变量中, with pytest.warns((RuntimeWarning, UserWarning)) as warn_info: warnings.warn("my warning aaaaa", RuntimeWarning) warnings.warn("value must be 0 or None", UserWarning) print('11111111111111') # warn_info对象的成员,它是列表 print(warn_info.__dict__) print(len(warn_info)) print(warn_info[0].__dict__) # 第0个对象的成员信息 print(warn_info[0].category) print(warn_info[0].message) def test_3(): # 断言: 警告为RuntimeWarning,通过内容包含warning with pytest.warns(RuntimeWarning, match='warning'): warnings.warn("my warning", RuntimeWarning) if __name__ == '__main__': # 默认不打印,需要加上-s参数, []中元素是字符串 pytest.main(['-s'])

3、setup和teardown函数

函数级别:

作用域为:普通函数setup_function, teardown_function,普通测试用例函数前后执行一次

类级别

作用域为类:类、实例方法setup_class 和 teardown_class , 类的前后执行1次setup_method, teardown_method,测试用例实例方法前后各执行1次

模块级别

作用域:运行的文件setup_module(), teardown_module() 定义在类外文件的前后,执行1次

4、fixture基本使用

4.1基础

fixture是pytest特有的功能,它以装饰器形式定义在函数上面, 在编写测试函数的时候,可以将被fixture装饰的函数的名字做为测试函数的参数,运行测试脚本时,执行测试函数时就会自动传入被fixture装饰的函数的返回值。

import pytest import requests # 1. @pytest.fixture装饰一个函数,非测试用例 @pytest.fixture() def get_web(): print('get_web111111111') return 'https://www.baidu.com' # 2. pytest.fixture装饰函数的函数名,作为测试用例的参数 def test_1(get_web): # 3. 测试用例函数里面使用到pytest.fixture装饰函数的函数名,使用的是这个函数的返回值 print('aaaa = ', get_web) resp = requests.get(get_web) print(resp.status_code) def test_2(get_web): print('test_2 = ', get_web) # 执行流程 # 1. 执行测试用例,测试用例有get_web参数 # 2. 先执行get_web函数后,再执行测试用例里面的代码 if __name__ == '__main__': # 默认不打印,需要加上-s参数, []中元素是字符串 pytest.main(['-s'])

4.2conftest.py文件

共享fixture函数

如果在测试中多个测试文件中用例用到同一个的fixture函数,则可以将其移动到conftest.py文件中,所需的fixture对象会自动被pytest发现,而不需要再每次导入

conftest.py文件名固定

在conftest.py文件中实现共用的fixture函数

拓展:pytest.mark.usefixtures

5、fixture参数

pytest.fixture(scope='function', params=None, autouse=False, ids=None, name=None)

scope: 被标记方法的作用域, 可以传入以下四个值; “function”: 默认值,每个测试用例都要执行一次 fixture 函数 “class”: 作用于整个类, 表示每个类只运行一次 fixture 函数 “module”: 作用于整个模块, 每个 module 的只执行一次 fixture 函数 “session”: 作用于整个 session , 一次 session 只运行一次 fixture

params: list 类型,默认 None, 接收参数值,对于 param 里面的每个值,fixture 都会去遍历执行一次。

autouse: 是否自动运行,默认为 false, 为 true 时此 session 中的所有测试函数都会调用 fixture

测试代码示例:

import pytest # scope='class': # 如果测试用例加上fixture修饰函数作为形参, # 对于类中测试用例,只会在第一次的时候执行 @pytest.fixture(scope='class') def func(): print('fixture修饰的func') # ============================ # 普通函数测试用例 def test_1(func): print('test_1111') # 类实例方法测试用例 class TestDemo(object): def test_2(self, func): print('test222222222222222') def test_3(self, func): print('test33333333333333') if __name__ == '__main__': # 默认不打印,需要加上-s参数, []中元素是字符串 pytest.main(['-s')

运行结果:

6、pytest.mark标记

pytest.mark下提供了标记装饰器,除了之前我们使用的pytest.mark.usefixtures()装饰器以外,还有一些常用的标记装饰器

装饰器作用pytest.mark.xfail()将测试函数标记为预期失败。pytest.mark.skip()无条件地跳过测试函数pytest.mark.skipif()有条件地跳过测试函数pytest.mark.parametrize()参数化Fixture方法和测试函数。pytest.mark.usefixtures()使用类、模块或项目中的Fixture方法。

示例:

就算标志,测试用例,还是可以执行,只是结果标志为X import pytest @pytest.mark.xfail(reason='功能不完善,标志为失败') def test_1(): print('1111111111111') num = 1 + 1 assert num == 2, 'num不为2,所以测试不通过'

import pytest # @pytest.mark.skip(reason='之前注册,无需再注册') # 无条件跳过 #@pytest.mark.skipif(condition = True , reason='之前注册,无需再注册') # 条件跳过, 和上面一样 @pytest.mark.skipif(condition = False , reason='需要注册,无需跳过') # skipif,为False,这句话没有意义 def test_1(): print('注册功能') import pytest @pytest.mark.parametrize(['usrname', 'pwd'], [('mike', '123'), ('yoyo', 'abc')]) def test_login(usrname, pwd): print('usrname = %s, pwd = %s' % (usrname, pwd))

7、pytest配置文件

通常在编写测试代码的目录下新建配置文件pytest.ini

[pytest] ;指定运行的测试文件 addopts = -s test.py ;需要测试文件的路径 testpaths = ./ ;识别test开头的py文件 python_files = test*.py ;识别Test开头的类 python_classes = Test* ;识别test开头的函数 python_functions = test* ;在ini文件中注释语句是以分号开始的, 所有的注释语句不管多长都是独占一行直到结束的

8、常用插件

#1、生成测试报告 pip install pytest-html -i https://pypi.tuna.tsinghua.edu.cn/simple #2、控制函数执行顺序 pip install pytest-ordering -i https://pypi.tuna.tsinghua.edu.cn/simple #3、失败重试 pip install pytest-rerunfailures -i https://pypi.tuna.tsinghua.edu.cn/simple

8.1、生成测试报告

修改配置文件(指定运行的文件,指定生成的测试报告的路径),然后在终端使用命令pytest运行测试文件

[pytest] addopts = -s test.py --html=./report.html

测试代码:

def test1(): print('test1') assert True def test2(): print('test2') assert False

生成的测试报告示意:

8.2、控制函数执行顺序

注意: unittest的执行顺序的按照ASCII的排序执行的。 pytest使用装饰器:@pytest.mark.run(order=num),数字靠前的先运行。

import pytest @pytest.mark.run(order=3) def test_1(): print('111111111111') @pytest.mark.run(order=1) # 数字靠前的先运行 def test_2(): print('222222222222') @pytest.mark.run(order=2) def test_3(): print('3333333333333')

结果:test2被装饰的数字是1,所以先执行

8.3、失败重试

安装依赖pip install pytest-rerunfailures

测试用例不通过的,才会重试比如:终端执行pytest -s test.py --reruns 3(重复执行三次)

8.4按名称取消插件

在终端执行命令测试代码的时候,或者在配置文件pytest.ini中添加-p no:插件名的方式可以取消插件

比如执行的时候,取消执行顺序的插件pytest -s test_21_report.py -p no:ordering

9、yaml

9.1、理论

Yaml 是一种所有编程语言可用的友好的数据序列化标准,类似于 json。其语法和其他高阶语言类似, 并且可以简单表达列表、字典、标量等资料形式。

语法规则

大小写敏感 使用缩进表示层级关系 缩进时不允许使用tab键,只允许使用空格 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可

支持的数据结构

字典: 键值对的集合 数组: 一组按照次序排列的值, 又称序列(sequence)、列表 纯量: 单个的、不可再分的值, 包括 字符串、布尔值、整数、浮点数、null、日期

yaml转换工具:

工具1工具2

9.2、使用python处理yaml文件

PyYaml库的安装pip3 install -U PyYAML

1、读取yaml文件数据yaml.load()

准备一个yaml文件编写转换格式的python代码 import yaml # 1. 只读方式打开文件,文件对象 # 2. 读取内容 = yaml.load(文件对象, Loader=yaml.FullLoader) with open('./info.yaml''r') as f: ret = yaml.load(f, Loader=yaml.FullLoader) print(ret)

2、python写入生成yaml文件yaml.dump()

import yaml # 准备一个字典数据 data={'Search_Data': { 'search_test_002': {'expect': {'value': '你好'}, 'value': '你好'}, 'search_test_001': {'expect': [4, 5, 6], 'value': 456} } } # 1. 只写方式,指定utf-8编码打开文件,有一个文件对象 # 2. yaml.dump(写入文件的数据, 上一步的文件对象, allow_unicode=True) with open('xxx.yaml', 'w', encoding='utf-8') as f: yaml.dump(data, f, allow_unicode=True)
最新回复(0)