Pytest-编写测试函数

2 编写测试函数

2.1 使用 assert 声明

pytest 允许在 assert 关键字后面添加任何表达式。如果表达式的值通过 bool 转换后等于 False,则意味着测试失败。

pytest 有一个重要功能是可以重写 assert 关键字,pytest 会截断对原生 assert 的调用,替换为 pytest 定义的 assert,从而提供更多的失败信息和细节。

刚开始对重写assert关键字的概念不是很理解,在网上找了下,有个pytest.register_assert_rewrite的断言重写概念,

大概意思就是在其他模块中使用了assert断言,在将这个模块导入pytest的项目时,需要启用断言重写,让pytest在导入之前明确要求重写这个模块,详情见我的另一篇随笔

2.2 预期异常

1 import pytest
2 import tasks
3 
4 
5 def test_add_raises():
6     """add() should raise an exception with wrong type param."""
7     with pytest.raises(TypeError):
8         tasks.add(task='not a Task object')

测试用例 test_add_raises() 中有 with pytest.raises(TypeError) 声明,意味着无论 with 中的内容是什么,都至少会发生 TypeError 异常。

如果测试通过,说明确实发生了我们预期的 TypeError 异常;如果抛出的是其他类型的异常,则与我们所预期的不一致,说明测试失败。

2.3 测试函数的标记

pytest 提供了标记机制,允许你使用 marker 对测试函数做标记。一个测试函数可以有多个 marker,一个 marker 也可以用来标记多个测试函数。

 1 # test_api_exceptions.py
 2 
 3 @pytest.mark.smoke
 4 def test_list_raises():
 5     """list() should raise an exception with wrong type param."""
 6 
 7 
 8 @pytest.mark.get
 9 @pytest.mark.smoke
10 def test_get_raises():
11     """get() should raise an exception with wrong type param."""
>> pytest -v -m 'smoke' test_api_exceptions.py

指定 -m 'smoke' 会运行标记为 @pytest.mark.smoke 的两个测试;而指定 -m 'get',会运行标记为 @pytest.mark.get 的那个测试。

-m 后面也可以使用表达式,可以在标记之间添加 and、or、not 关键字。

2.4 跳过测试

skip 和 skipif 允许你跳过不希望运行的测试。 

要跳过某个测试,只需要简单地在测试函数上方添加 @pytest.mark.skip() 装饰器即可。

实际上可以给要跳过的测试添加理由和条件,比如希望它只在包版本低于0.2.0时才生效,这时应当使用 skipif 来替代 skip

# test_unique_id_3.py

@pytest.mark.skipif(tasks.__version__ < '0.2.0',
                    reason='not supported until version 0.2.0')
def test_unique_id_1():
>> pytest -rs test_unique_id_3.py

使用 -rs,可以让用户知道用例跳过的原因

-r chars

show extra test summary info as specified by chars: (f)ailed, (E)rror, (s)kipped, (x)failed, (X)passed, (p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll.
(w)arnings are enabled by default (see --disable-warnings), 'N' can be used to reset the list. (default: 'fE').

该选项不但可以帮助用户了解某些测试被跳过的原因,还可以用于查看其他测试结果。

2.5 标记预期会失败的用例

 使用 xfail 标记,告诉 pytest 运行此测试,我们预期它会失败。@pytest.mark.xfail()

2.6 参数化测试

向函数传值并检验输出结果是软件测试的常用手段,但是对大部分功能测试而言,仅仅使用一组数据是无法充分测试函数功能的。参数化测试允许传递多组数据,一旦发现测试失败,pytest会及时报告。

 1 @pytest.mark.parametrize('task',
 2                          [Task('sleep', done=True),
 3                           Task('wake', 'brian'),
 4                           Task('breathe', 'BRIAN', True),
 5                           Task('exercise', 'BrIaN', False)])
 6 def test_add_2(task):
 7     """Demonstrate parametrize with one parameter."""
 8     task_id = tasks.add(task)
 9     t_from_db = tasks.get(task_id)
10     assert equivalent(t_from_db, task)

paramtrize()的第一个参数是用逗号分隔的字符串列表,本例中只有一个'task';第二个参数是一个值列表,本例中是一个Task对象列表。pytest会轮流对每个task做测试,并分别报告每一个测试用例的结果。

 1 @pytest.mark.parametrize('summary, owner, done',
 2                          [('sleep', None, False),
 3                           ('wake', 'brian', False),
 4                           ('breathe', 'BRIAN', True),
 5                           ('eat eggs', 'BrIaN', False),
 6                           ])
 7 def test_add_3(summary, owner, done):
 8     """Demonstrate parametrize with multiple parameters."""
 9     task = Task(summary, owner, done)
10     task_id = tasks.add(task)
11     t_from_db = tasks.get(task_id)
12     assert equivalent(t_from_db, task)

你可以使用完整的测试标识来重新指定需要运行的测试。

>> pytest -v test_add_variety.py::test_add_3[sleep-None-False]

如果标识中包含空格,别忘了添加引号。

>> pytest -v "test_add_variety.py::test_add_3[eat eggs-BrIaN-False]"

另一种方式

 1 tasks_to_try = (Task('sleep', done=True),
 2                 Task('wake', 'brian'),
 3                 Task('wake', 'brian'),
 4                 Task('breathe', 'BRIAN', True),
 5                 Task('exercise', 'BrIaN', False))
 6 
 7 
 8 @pytest.mark.parametrize('task', tasks_to_try)
 9 def test_add_4(task):
10     """Slightly different take."""
11     task_id = tasks.add(task)
12     t_from_db = tasks.get(task_id)
13     assert equivalent(t_from_db, task)

为了改善可读性,我们为parametrize()引入一个额外参数ids,使列表中的每个元素都被标识。ids是一个字符串列表,它和数据对象列表的长度保持一致。

 1 task_ids = ['Task({},{},{})'.format(t.summary, t.owner, t.done)
 2             for t in tasks_to_try]
 3 
 4 
 5 @pytest.mark.parametrize('task', tasks_to_try, ids=task_ids)
 6 def test_add_5(task):
 7     """Demonstrate ids."""
 8     task_id = tasks.add(task)
 9     t_from_db = tasks.get(task_id)
10     assert equivalent(t_from_db, task)

在给@pytest.mark.parametrize()装饰器传入列表参数时,还可以在参数值旁边定义一个id来做标识,语法是 pytest.param(<value>, id='something')。

1 @pytest.mark.parametrize('task', [
2     pytest.param(Task('create'), id='just summary'),
3     pytest.param(Task('inspire', 'Michelle'), id='summary/owner'),
4     pytest.param(Task('encourage', 'Michelle', True), id='summary/owner/done')])
5 def test_add_6(task):
6     """Demonstrate pytest.param and id."""
7     task_id = tasks.add(task)
8     t_from_db = tasks.get(task_id)
9     assert equivalent(t_from_db, task)

你也可以为测试类加上 parametrize() 装饰器,这种情况下,该数据集会被传递给该类的所有类方法。

 1 @pytest.mark.parametrize('task', tasks_to_try, ids=task_ids)
 2 class TestAdd():
 3     """Demonstrate parametrize and test classes."""
 4 
 5     def test_equivalent(self, task):
 6         """Similar test, just within a class."""
 7         task_id = tasks.add(task)
 8         t_from_db = tasks.get(task_id)
 9         assert equivalent(t_from_db, task)
10 
11     def test_valid_id(self, task):
12         """We can use the same data for multiple tests."""
13         task_id = tasks.add(task)
14         t_from_db = tasks.get(task_id)
15         assert t_from_db.id == task_id

 

posted on 2022-05-18 21:46  ZouYus  阅读(86)  评论(0编辑  收藏  举报