pytest目录
安装依赖包:
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转换工具:
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)