文章目录
第 2 章 编写测试函数
第1章介绍了 pytest 是如何工作的,学习了如何指定测试目录、使用命令行选项。本章讲解如何为 python 程序包编写测试函数。
2.1 测试示例程序
以 Tasks 项目来演示如何为 Python 程序包编写测试。
Tasks 项目获取:https://pragprog.com/titles/bopytest/source_code
下面来看 Tasks 项目的目录结构:
src 是包含源码的文件夹,所有的测试都放在 tests 文件夹。其他四个根目录文件在附录 D 中详细介绍。项目目录中包含两类 __init__.py 文件,一类在 src 目录下,一类在 tests 目录下。src/tasks/__init__.py 告诉Python解释器该目录是Python包。此外,执行到 import tasks 时,这个 __init__.py 将作为该包主入口。
测试目录中,功能测试和单元测试分别放在不同的目录下:func、unit。test/func/__init__.py 和 test/unit/__init__.py 都是空文件,他们的作用是给pytest提供搜索路径,找到测试根目录以及 pytest.ini 文件,这个文件是可选的,保存了pytest在该项目下的特定配置。conftest.py也是可选的,是pytest的“本地插件库”,其中可以包含hook函数和fixture(第3、4、5章介绍)。
2.1.1 本地安装 Tasks 项目程序包
切换到 tasks_proj 根目录,运行以下命令:
pip install .(仅安装测试)
或者 pip install -e .(安装好希望修改源码重新安装就需要用-e(editable)选项)
安装完成之后,运行如下测试文件:
# F:\pytest\my_env\tasks_proj\tests\unit\test_task.py
from tasks import Task
def test_asdict():
"""_asdict() should return a dictionary."""
t_task = Task('do something', 'okken', True, 21)
t_dict = t_task._asdict()
expected = {'summary': 'do something',
'owner': 'okken',
'done': True,
'id': 21}
assert t_dict == expected
def test_replace():
"""replace() should change passed in fields."""
t_before = Task('finish book', 'brain', False, None)
t_after = t_before._replace(id=10, done=True)
t_expected = Task('finish book', 'brain', True, 10)
assert t_after == t_expected
def test_defaults():
"Using no parameters should invoke defaults."
t1 = Task()
t2 = Task(None, None, False, None)
assert t1 == t2
def test_member_access():
"""Check .filed functionality of namedtuple."""
t = Task('buy milk', 'brian')
assert t.summary == 'buy milk'
assert t.owner == 'brian'
assert (t.done, t.id) == (False, None)
2.2 使用 assert 语句
用 pytest 测试时,若需要传递测试失败信息,可以直接使用 Python 自带的 assert 关键字,Python 允许在 assert 后面添加任何表达式。如果表达式的值通过 bool 转换后等于 False,则意味着测试失败。
pytest 的一个很重要的功能:重写 assert 关键字。pytest 会截断对原生assert的调用,替换为pytest定义的assert,从而提供更多的失败信息和细节。
2.3 预期异常
Tasks 项目中的 API 中,有几个地方可能抛出异常。
cli.py 中的 CLI 代码 和 api.py 中的 API 代码 统一指定了发送给 API 函数的数据类型,假设检查到数据类型错误,异常很可能就是由这些 API 函数抛出的。
为确保文件中的函数在发生类型错误时可以抛出异常,下面来做一些检验:在测试中使用错误类型的数据,引起 TypeError 异常。
# F:\pytest\my_env\tasks_proj\tests\func\test_api_exceptions.py
import pytest
import tasks
def test_add_raises():
"""add() should raise an exception with wrong type param."""
with pytest.raises(TypeError):
tasks.add(task='not a Task object.') # 传参数据的“类型异常”
测试中有 with pytest.raises(TypeError) 声明,意味着无论 with 中的内容是什么,都至少会发生 TypeError 异常。如果测试通过,说明确实发生了我们预期的 TypeError 异常;如果抛出的是其他类型的异常,则与我们所预期的不一致,说明测试失败。
对于 start_tasks_db(db_path, db_type) 来说,db_type 不单要求是字符串类型,还必须为 “tiny” 或 “mongo”。
在同一个文件中编写另一