Pytest介绍
Pytest是一个成熟的全功能Python测试工具,可以帮助您编写更好的程序。该pytest框架使编写可读测试变得容易,并且可以扩展以支持应用程序和库的复杂功能测试。pytest需要:Python 3.7+ 或 PyPy3。
Pytest特点
开箱即用.自动发现测试用例简化断言语句,统一为assert丰富的插件架构,超过 800 多个外部插件具有灵活的扩展性和方便的参数化方法
安装&使用
$ pip install pytest
$ pytest --version
pytest 7.1.2
命名规则&断言
- 模块:test_.py 或_test.py。
- 函数:test*。
- 类:Test*,测试类不能有init函数。
- 断言:assert ,支持所有python的布尔表达式
用例执行顺序
- 在包含用例的项目根路径下(root-dir)执行:pytest -v
- 目录和py文件:按照ascii码排序方式,进行文件递归顺序收集和运行
- py文件中:按照代码从上到下
- 验证执行顺序:pytest --collect-only
命令行选项
assert 布尔表达式
-
[x] 比较运算符:【>】【<】【<=】【>=】【==】【!=】
例如:assert actual == expected -
[x] 身份和成员运算符: 【is】、【is not】、【in】、【not in】
例如: assert expected in actual -
[x] 逻辑运算符和布尔函数:【not】【and】【or】【startswith()】
例如 :assert not expected
例如 :assert version.startswith("3.8")
待测student功能
# ---------------------------------student.py--------------------------------------
# 以字典的形式返回学生的姓名和成绩
def student_score():
return {'小明': 99, '小红': 100}
# 以列表的形式返回学生的姓名
def student_name():
return ['小明', '小红']
编写pass 与fail 用例
# ---------------------------------test_01_pass_fail.py---------------------------
# 这是通过用例
def test_pass():
# 期望值
expected = 1
# 实际值
actual = 1
# 通过(实际值)跟(预期值)进行相等比较,相等通过,不相等报错
assert actual == expected
# 这是失败,不通过用例
def test_fail():
# 期望值
expected = 2
# 实际值
actual = 3
# 通过(实际值)跟(预期值)进行相等比较,相等通过,不相等报错
assert actual == expected
# ---------------------------------test_02_datatype.py----------------------------
from btest.Day1.student import student_score, student_name
# 这是student_score的验证全数据等式用例
def test_student_score():
# 期望值
expected = {'小红': 100, '小明': 99}
# 实际值
actual = student_score()
# 通过(实际值)跟(预期值)进行相等比较,相等通过,不相等报错
assert actual == expected
# 这是student_score 的验证部分数据等式用例
# 验证小红是否等于100,是:pass 、否:fail
def test_student_score_other():
# 期望值
expected = 100
# 实际值
actual = student_score()['小红']
# 通过(实际值)跟(预期值)进行相等比较,相等通过,不相等报错
assert actual == expected
# 这是student_name 的包含用例:
# 验证小明是否在学生姓名列表中,是:pass(通过), 否:fail(失败)
def test_student_name():
# 期望值
expected = '小明123123'
# 实际值
actual = student_name()
# 通过判断(预期数据)是否在实际数据中,在:pass,不在:fail
assert len(actual) == 2
assert expected in actual
多断言
# ---------------------------------test_03_many_assert.py-------------------------
# 安装: pip install pytest-assume -i https://pypi.douban.com/simple
from pytest_assume.plugin import assume
from btest.Day1.student import student_score
def test_student_score_hard():
actual = student_score()
with assume: # 实际数据类型是否跟预期数据类型一致。
assert isinstance(actual, dict)
with assume: # 实际的数据长度是否跟预期长度相同
assert len(actual) == 3
with assume: # 预期数据是否在实际数据内
assert '小红123123' in actual
with assume: # 实际数据是否等于预期数据
assert actual['小红'] == 60
自定义异常信息
# ---------------------------------test_04_custom_fail.py-------------------------
import pytest
from pytest_assume.plugin import assume
from btest.Day1.student import student_name
def test_student_name_hard_custom_fail():
actual = student_name()
with assume: # 实际的数据长度是否跟预期长度相同
assert len(actual) == 3, \
f"实际数据长度:{len(actual)} 与预期数据长度:{3}不相同!!"
with assume: # 预期数据是否在实际数据内
assert '小红' in actual, \
f"预期数据:{'小红'}没在实际数据中:{actual}!!"
if '小乐' not in actual:
pytest.fail(f"预期数据:小乐,没有在实际数据{actual}中")
处理异常
# ---------------------------------test_05_exceptions.py--------------------------
import pytest
from btest.Day1.student import student_score
# 通用try/finally 处理用例异常
def test_student_score_error():
actual = student_score()
try:
actual['小红'] = actual['小红'] + '10'
except:
pass
finally:
assert '小乐' in actual, "小乐没有在学生成绩表里!!"
# pytest用例异常处理
def test_student_score_raise():
actual = student_score()
with pytest.raises(Exception): # 更具可读性且不易出错。
actual['小红'] = actual['小红'] + '10'
# assert '小乐' in actual # 不会执行
# 会执行
assert '小乐' in actual, \
f" 预期数据:小乐,没有在实际数据中{actual}!!"
# ---------------------------------test_06_get_error.py---------------------------
"""
1、获取用例的所在的模块、
2、获取用例的名称、
3、获取用例的具体错误信息。
"""
import inspect # 从实时 Python 对象中获取有用的信息
import pytest
# python-inspect 捕获详细异常信息
def test_inspect_info():
# 期望值为 模块名::用例名::错误信息
expected = 'test_06_get_error::test_inspect_info::division by zero'
with pytest.raises(Exception) as exc_info:
c = 2 / 0
# 获取错误文本:exc_info.value.args[0]
# 如获取错误类型:exc_info.type
error_info = exc_info.value.args[0] # 获取用例具体错误信息
module_name = inspect.getmodulename(__file__) # 获取用例所在模块
method_name = inspect.stack()[0][3] # [0][3]获取用例名字,[1][4][0]获取用例名字和参数
actual = f"{module_name}::{method_name}::{error_info}"
assert actual == expected
# Pytest内置功能捕获异常信息
def test_get_raises(request):
# 期望值为 模块名::用例名::错误信息
expected = 'test_06_get_error::test_get_raises::division by zero'
with pytest.raises(Exception) as exc_info:
c = 2 / 0
error_info = exc_info.value.args[0]
module_name = request.module.__name__.split('.')[-1] # 获取模块 包名.test_06xxx
method_name = request.function.__name__ # 获取用例名字
actual = f"{module_name}::{method_name}::{error_info}"
assert actual == expected
数据类
Dataclass这个模块提供了一个类装饰器和一些函数,用于自动添加生成的 special method,例如 init() 和 repr() 到用户定义的类。 它最初描述于 PEP 557 .
对标准库的补充,称为数据类。可以将数据类视为“具有默认值的可变命名元组”
数据类使用普通的类定义语法,你可以自由地使用继承、元类、文档字符串、用户定义的方法、类工厂和其他 Python 类特性.
装饰器: https://www.ztloo.com/2021/11/06/decorators/
Python术语对照社区文档: https://docs.python.org/zh-cn/3/glossary.html?highlight=decorator
special method(特殊方法) :https://docs.python.org/zh-cn/3/glossary.html#term-special-method
PEP 557(Python 增强建议) :https://www.python.org/dev/peps/pep-0557
标准库:https://docs.python.org/zh-cn/3/library/index.html
命名元组: https://docs.python.org/zh-cn/3/library/collections.html?highlight=namedtuple
# ---------------------------------face_to_object.py------------------------------
from dataclasses import asdict, dataclass, astuple, replace
# https://docs.python.org/3/library/dataclasses.html?highlight=dataclass#module-dataclasses
@dataclass
class GirlFriend:
# 节省了__init__函数
name: str
age: int
height: int = 170 # cm
weight: int = 50 # kg
@classmethod
def from_dict(cls, d):
return GirlFriend(**d)
def to_dict(self):
return asdict(self)
def to_tuple(self):
return astuple(self)
def update(self, **changes):
return replace(self, **changes)
if __name__ == '__main__':
# 把字典转行成类对象
ym_dict = {'name': '杨幂', 'age': 18, 'height': 170, 'weight': 68}
ym = GirlFriend.from_dict(ym_dict)
print('我是类对象型杨幂', ym)
# 把类对象转化成字典
ym_dict = ym.to_dict()
print('我是字典型杨幂:', ym_dict)
# 把类对象转化成元组
ym_tuple = ym.to_tuple()
print(f'我是元组型杨幂:{ym_tuple}')
# 把杨幂修改成刘亦菲
lyf = ym.update(name='刘亦菲', age=22)
print(f'我是刘亦菲:{lyf}')
自定义辅助断言函数
import pytest
from btest.Day2.face_to_object import GirlFriend
def assert_identical(gf_1, gf_2):
if gf_1.name != gf_2.name:
pytest.fail(f"name不匹配. {gf_1.name} != {gf_2.name}")
def test_identical():
gf_1 = GirlFriend("刘亦菲", 18, 170, 60)
gf_2 = GirlFriend("刘亦菲", 18, 170, 60)
assert_identical(gf_1, gf_2)