pytest功能入门

pytest作为python的测试框架,具有易于上手、功能强大、可扩展性好、兼容性强、效率高、第三方插件丰富等特点。

命名

pytest能识别的测试的函数、类、方法、模块甚至是代码文件,默认都是以 test_* 开头或是以 *_test 结尾,这是为了遵守标准的测试约定。

当然我们也可以在 pytest 的配置文件pytest.ini中修改不同的前缀或后缀名:

# content of pytest.ini
# Example 1: have pytest look for "check" instead of "test"
[pytest]
python_files = check_*.py
python_classes = Check
python_functions = *_check

标记(mark)

在 pytest 中,mark 标记是一个十分好用的功能,通过标记的装饰器来装饰我们的待测试对象,让 pytest在测试时会根据 mark 的功能对我们的函数进行相应的操作。

默认情况下,pytest 会递归查找当前目录下所有以 test 开始或结尾的 Python 脚本,并执行文件内的所有以 test 开始或结束的函数和方法。

1、如果你想指定运行测试用例,可以通过 :: 显式标记(文件名:: 类名::方法名)。

pytest test_sample.py::test_pass

2、如果你想选择一些测试用例,可以使用 -k 模糊匹配。

pytest -k pass test_sample.py

3、如果你想跳过个别测试用例,可以使用 pytest.mark.skip(),或者 pytest.mark.skipif(条件表达式)。

# 测试失败
@pytest.mark.skip()
def test_fail():
    assert func(3) == 5

4、如果你想捕捉一些异常,可以使用pytest.raises()。

# test_raises.py

def test_raises():
    with pytest.raises(TypeError) as e:
        connect('localhost', '6379')
    exec_msg = e.value.args[0]
    assert exec_msg == 'port type must be int'

5、如果你事先知道测试函数会执行失败,但又不想直接跳过,而是希望显示的提示,可以使用pytest.mark.xfail()。

# 测试失败
@pytest.mark.xfail()
def test_fail():
    assert func(3) == 5

6、如果你想对某个测试点进行多组数据测试,可以使用 pytest.mark.parametrize(argnames, argvalues) 参数化测试,即每组参数都独立执行一次测试。

注意:以往我们可以把这些参数写在测试函数内部进行遍历,但是当某组参数导致断言失败,测试则就终止了。

# 测试成功
@pytest.mark.parametrize('data', [1, 2, 3])
def test_pass(data):
    assert func(data) == 4

Fixture

举例:

通常我们会通过一个数据库类创建一下数据库对象,然后使用前先进行连接 connect(),接着进行操作,最后使用完之后断开连接 close() 以释放资源。

# test_fixture.py

import pytest

class Database(object):

    def __init__(self, database):
        self.database = database
    
    def connect(self):
        print(f"\n{self.database} database has been connected\n")

    def close(self):
        print(f"\n{self.database} database has been closed\n")

    def add(self, data):
        print(f"`{data}` has been add to database.")
        return True

@pytest.fixture
def myclient():
    db = Database("mysql")
    db.connect()
    yield db
    db.close()

def test_foo(myclient):
    assert myclient.add(1) == True

在这段代码中,实现fixture的关键是 @pytest.fixture 这一行装饰器代码,通过该装饰器我们可以直接使用一个带有资源的函数将其作为我们的fixture,在使用时将函数的签名(即命名)作为参数传入到我们的测试用例中,在运行测试时 pytest 则会自动帮助我们进行注入。

在注入的过程中 pytest 会帮我们执行 myclient() 中 db 对象的 connect() 方法调用模拟数据库连接的方法,在测试完成之后会再次帮我们调用 close() 方法释放资源。

pytest 的 fixture 机制是一个让我们能实现复杂测试的关键,试想我们以后只需要写好一个带有测试数据的 fixture,就可以在不同的模块、函数或者方法中多次使用,真正做到「一次生成,处处使用」。当然 pytest 给我们提供了可调节载具作用域(scope)的情况,从小到大依次是:

  • function:函数作用域(默认)
  • class:类作用域
  • module:模块作用域
  • package:包作用域
  • session:会话作用域

fixture会随着作用域的生命周期而诞生、销毁。所以如果我们希望创建的fixture作用域范围增加,就可以在 @pytest.fixture() 中多增加一个 scope 参数,从而提升fixture作用的范围。

 

上一篇:解决:PytestUnknownMarkWarning: Unknown pytest.mark.webtest - is this a typo?


下一篇:2021-09-07面试题 16.15. 珠玑妙算