Pytest 基础教程
文章目录
- 第一部分:快速入门
- 一、快速入门
- 1. 安装 Pytest
- 2. 第一个测试函数
- 3. 运行测试函数
- 4. 测试失败
- 第二部分:测试函数
- 一、断言
- 二、捕获异常
- 三、标记函数
- 1.Pytest 查找测试策略
- 2. 标记测试函数
- 四、跳过测试
- 五、预见错误
- 六、 参数化
- 第三部分:固件
- 一、 什么是固件
- 二、预处理和后处理
- 三、作用域
- 四、自动执行
- 五、重命名
- 六、参数化
- 七、内置固件
- tmpdir & tmpdir_factory
- pytestconfig
- capsys
- monkeypatch
- recwarn
第一部分:快速入门
一、快速入门
1. 安装 Pytest
使用 pip 进行安装:
$ pip install pytest
2. 第一个测试函数
Pytest 使用 Python 的 assert
进行条件判断,最简单的测试函数如:
# test1.pydef test_passing():assert (1, 2, 3) == (1, 2, 3)
3. 运行测试函数
使用命令 pytest
运行测试函数:
$ pytest tests/test1.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 1 itemtests\test1.py . [100%]========================== 1 passed in 0.09 seconds ===========================
注解
pytest 使用 .
标识测试成功(PASSED
)。
小技巧
可以使用 -v
选项,显示测试的详细信息。
使用 pytest -h
查看 pytest 的所有选项。
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0 -- c:\anaconda3\python.exe
cachedir: .pytest_cache
rootdir: F:\self-repo\learning-pytest, inifile:
collected 1 itemtests/test1.py::test_passing PASSED [100%]========================== 1 passed in 0.03 seconds ===========================
4. 测试失败
下面是一个失败的测试函数:
# test2.pydef test_failing():assert (1, 2, 3) == (3, 2, 1)
运行结果为:
$ pytest tests/test2.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 1 itemtests\test2.py F [100%]================================== FAILURES ===================================
________________________________ test_failing _________________________________def test_failing():
> assert (1, 2, 3) == (3, 2, 1)
E assert (1, 2, 3) == (3, 2, 1)
E At index 0 diff: 1 != 3
E Use -v to get the full difftests\test2.py:2: AssertionError
========================== 1 failed in 0.19 seconds ===========================
注解
pytest 使用 F
标识测试失败(FAILED
)。
pytest 对失败的测试给出了非常人性化的提示。
第二部分:测试函数
一、断言
在 pytest 中,assert
是编写测试的最基础工具。如:
assert a == bassert a <= b
具体的 assert
语法参考 The assert statement 。
二、捕获异常
在测试过程中,经常需要测试是否如期抛出预期的异常,以确定异常处理模块生效。在 pytest 中使用 pytest.raises()
进行异常捕获:
# test_raises.pydef 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'
运行结果如下:
$ pytest test_raises.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 1 itemtests\test-function\test_raise.py . [100%]========================== 1 passed in 0.07 seconds ===========================
三、标记函数
1.Pytest 查找测试策略
默认情况下,pytest 会递归查找当前目录下所有以 test
开始或结尾的 Python 脚本,并执行文件内的所有以 test
开始或结束的函数和方法。
对于下面脚本:
# test_no_mark.pydef test_func1():assert 1 == 1def test_func2():assert 1 != 1
直接执行测试脚本会同时执行所有测试函数:
$ pytest tests/test-function/test_no_mark.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 2 itemstests\test-function\test_no_mark.py .F [100%]================================== FAILURES ===================================
_________________________________ test_func2 __________________________________def test_func2():
> assert 1 != 1
E assert 1 != 1tests\test-function\test_no_mark.py:6: AssertionError
===================== 1 failed, 1 passed in 0.07 seconds ======================
2. 标记测试函数
由于某种原因(如 test_func2
的功能尚未开发完成),我们只想执行指定的测试函数。在 pytest 中有几种方式可以解决:
第一种,显式指定函数名,通过 ::
标记。
$ pytest tests/test-function/test_no_mark.py::test_func1
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 1 itemtests\test-function\test_no_mark.py . [100%]========================== 1 passed in 0.02 seconds ===========================
第二种,使用模糊匹配,使用 -k
选项标识。
$ pytest -k func1 tests/test-function/test_no_mark.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 2 items / 1 deselectedtests\test-function\test_no_mark.py . [100%]=================== 1 passed, 1 deselected in 0.03 seconds ====================
注解
以上两种方法,第一种一次只能指定一个测试函数,当要进行批量测试时无能为力;第二种方法可以批量操作,但需要所有测试的函数名包含相同的模式,也不方便。
第三种,使用 pytest.mark
在函数上进行标记。
带标记的测试函数如:
# test_with_mark.py@pytest.mark.finished
def test_func1():assert 1 == 1@pytest.mark.unfinished
def test_func2():assert 1 != 1
测试时使用 -m
选择标记的测试函数:
$ pytest -m finished tests/test-function/test_with_mark.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 2 items / 1 deselectedtests\test-function\test_with_mark.py . [100%]=================== 1 passed, 1 deselected in 0.10 seconds ====================
使用 mark,我们可以给每个函数打上不同的标记,测试时指定就可以允许所有被标记的函数。
注解
一个函数可以打多个标记;多个函数也可以打相同的标记。
运行测试时使用 -m
选项可以加上逻辑,如:
$ pytest -m "finished and commit"$ pytest -m "finished and not merged"
四、跳过测试
上一节提到 pytest 使用标记过滤测试函数,所以对于那些尚未开发完成的测试,最好的处理方式就是略过而不执行测试。
按正向的思路,我们只要通过标记指定要测试的就可以解决这个问题;但有时候的处境是我们能进行反向的操作才是最好的解决途径,即通过标记指定要跳过的测试。
Pytest 使用特定的标记 pytest.mark.skip
完美的解决了这个问题。
# test_skip.py@pytest.mark.skip(reason='out-of-date api')
def test_connect():pass
执行结果可以看到该测试已被忽略:
$ pytest tests/test-function/test_skip.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 1 itemtests\test-function\test_skip.py s [100%]========================== 1 skipped in 0.13 seconds ==========================
注解
pytest 使用 s
表示测试被跳过(SKIPPED
)。
Pytest 还支持使用 pytest.mark.skipif
为测试函数指定被忽略的条件。
@pytest.mark.skipif(conn.__version__ < '0.2.0',reason='not supported until v0.2.0')
def test_api():pass
五、预见错误
如果我们事先知道测试函数会执行失败,但又不想直接跳过,而是希望显示的提示。
Pytest 使用 pytest.mark.xfail
实现预见错误功能:
# test_xfail.py@pytest.mark.xfail(gen.__version__ < '0.2.0',reason='not supported until v0.2.0')
def test_api():id_1 = gen.unique_id()id_2 = gen.unique_id()assert id_1 != id_2
执行结果:
$ pytest tests/test-function/test_xfail.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 1 itemtests\test-function\test_xfail.py x [100%]========================== 1 xfailed in 0.12 seconds ==========================
注解
pytest 使用 x
表示预见的失败(XFAIL
)。
如果预见的是失败,但实际运行测试却成功通过,pytest 使用 X
进行标记(XPASS
)。
六、 参数化
当对一个测试函数进行测试时,通常会给函数传递多组参数。比如测试账号登陆,我们需要模拟各种千奇百怪的账号密码。
当然,我们可以把这些参数写在测试函数内部进行遍历。不过虽然参数众多,但仍然是一个测试,当某组参数导致断言失败,测试也就终止了。
通过异常捕获,我们可以保证程所有参数完整执行,但要分析测试结果就需要做不少额外的工作。
在 pytest 中,我们有更好的解决方法,就是参数化测试,即每组参数都独立执行一次测试。使用的工具就是 pytest.mark.parametrize(argnames, argvalues)
。
这里是一个密码长度的测试函数,其中参数名为 passwd
,其可选列表包含三个值:
# test_parametrize.py@pytest.mark.parametrize('passwd',['123456','abcdefdfs','as52345fasdf4'])
def test_passwd_length(passwd):assert len(passwd) >= 8
运行可知执行了三次测试:
$ pytest tests/test-function/test_parametrize.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 3 itemstests\test-function\test_parametrize.py F.. [100%]================================== FAILURES ===================================
再看一个多参数的例子,用于校验用户密码:
# test_parametrize.py@pytest.mark.parametrize('user, passwd',[('jack', 'abcdefgh'),('tom', 'a123456a')])
def test_passwd_md5(user, passwd):db = {'jack': 'e8dc4081b13434b45189a720b77b6818','tom': '1702a132e769a623c1adb78353fc9503'}import hashlibassert hashlib.md5(passwd.encode()).hexdigest() == db[user]
使用 -v
执行测试
$ pytest -v tests/test-function/test_parametrize.py::test_passwd_md5
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0 -- c:\anaconda3\python.exe
cachedir: .pytest_cache
rootdir: F:\self-repo\learning-pytest, inifile:
collected 2 itemstests/test-function/test_parametrize.py::test_passwd_md5[jack-abcdefgh] PASSED [ 50%]
tests/test-function/test_parametrize.py::test_passwd_md5[tom-a123456a] PASSED [100%]========================== 2 passed in 0.04 seconds ===========================
如果觉得每组测试的默认参数显示不清晰,我们可以使用 pytest.param
的 id
参数进行自定义。
# test_parametrize.py@pytest.mark.parametrize('user, passwd',[pytest.param('jack', 'abcdefgh', id='User<Jack>'),pytest.param('tom', 'a123456a', id='User<Tom>')])
def test_passwd_md5_id(user, passwd):db = {'jack': 'e8dc4081b13434b45189a720b77b6818','tom': '1702a132e769a623c1adb78353fc9503'}import hashlibassert hashlib.md5(passwd.encode()).hexdigest() == db[user]
现在的执行结果为:
$ pytest -v tests/test-function/test_parametrize.py::test_passwd_md5_id
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0 -- c:\anaconda3\python.exe
cachedir: .pytest_cache
rootdir: F:\self-repo\learning-pytest, inifile:
collected 2 itemstests/test-function/test_parametrize.py::test_passwd_md5_id[User<Jack>] PASSED [ 50%]
tests/test-function/test_parametrize.py::test_passwd_md5_id[User<Tom>] PASSED [100%]========================== 2 passed in 0.07 seconds ===========================
第三部分:固件
一、 什么是固件
固件(Fixture)是一些函数,pytest 会在执行测试函数之前(或之后)加载运行它们。
我们可以利用固件做任何事情,其中最常见的可能就是数据库的初始连接和最后关闭操作。
Pytest 使用 pytest.fixture()
定义固件,下面是最简单的固件,只返回北京邮编:
# test_postcode.py@pytest.fixture()
def postcode():return '010'def test_postcode(postcode):assert postcode == '010'
固件可以直接定义在各测试脚本中,就像上面的例子。更多时候,我们希望一个固件可以在更大程度上复用,这就需要对固件进行集中管理。Pytest 使用文件 conftest.py
集中管理固件。
注解
在复杂的项目中,可以在不同的目录层级定义 conftest.py
,其作用域为其所在的目录和子目录。
重要
不要自己显式调用 conftest.py
,pytest 会自动调用,可以把 conftest 当做插件来理解。
二、预处理和后处理
很多时候需要在测试前进行预处理(如新建数据库连接),并在测试完成进行清理(关闭数据库连接)。
当有大量重复的这类操作,最佳实践是使用固件来自动化所有预处理和后处理。
Pytest 使用 yield
关键词将固件分为两部分,yield
之前的代码属于预处理,会在测试前执行;yield
之后的代码属于后处理,将在测试完成后执行。
以下测试模拟数据库查询,使用固件来模拟数据库的连接关闭:
# test_db.py@pytest.fixture()
def db():print('Connection successful')yieldprint('Connection closed')def search_user(user_id):d = {'001': 'xiaoming'}return d[user_id]def test_search(db):assert search_user('001') == 'xiaoming'
执行时使用 -s
阻止消息被吞:
$ pytest -s tests/fixture/test_db.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 1 itemtests\fixture\test_db.py Connection successful
.Connection closed========================== 1 passed in 0.02 seconds ===========================
可以看到在测试成功的 .
标识前后有数据库的连接和关闭操作。
小技巧
如果想更细的跟踪固件执行,可以使用 --setup-show
选项:
$ pytest --setup-show tests/fixture/test_db.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 1 itemtests\fixture\test_db.pySETUP F dbtests/fixture/test_db.py::test_search (fixtures used: db).TEARDOWN F db========================== 1 passed in 0.03 seconds ===========================
三、作用域
固件的作用是为了抽离出重复的工作和方便复用,为了更精细化控制固件(比如只想对数据库访问测试脚本使用自动连接关闭的固件),pytest 使用作用域来进行指定固件的使用范围。
在定义固件时,通过 scope
参数声明作用域,可选项有:
function
: 函数级,每个测试函数都会执行一次固件;class
: 类级别,每个测试类执行一次,所有方法都可以使用;module
: 模块级,每个模块执行一次,模块内函数和方法都可使用;session
: 会话级,一次测试只执行一次,所有被找到的函数和方法都可用。
注解
默认的作用域为 function
。
@pytest.fixture(scope='function')
def func_scope():pass@pytest.fixture(scope='module')
def mod_scope():pass@pytest.fixture(scope='session')
def sess_scope():pass@pytest.fixture(scope='class')
def class_scope():pass
最简单使用固件方式是作为测试函数参数:
# test_scope.pydef test_multi_scope(sess_scope, mod_scope, func_scope):pass
执行结果如下,可以清楚看到各固件的作用域和执行顺序:
$ pytest --setup-show tests/fixture/test_scope.py::test_multi_scope
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 1 itemtests\fixture\test_scope.py
SETUP S sess_scopeSETUP M mod_scopeSETUP F func_scopetests/fixture/test_scope.py::test_multi_scope (fixtures used: func_scope, mod_scope, sess_scope).TEARDOWN F func_scopeTEARDOWN M mod_scope
TEARDOWN S sess_scope========================== 1 passed in 0.10 seconds ===========================
对于类使用作用域,需要使用 pytest.mark.usefixtures
(对函数和方法也适用):
# test_scope.py@pytest.mark.usefixtures('class_scope')
class TestClassScope:def test_1(self):passdef test_2(self):pass
执行结果如下,可见所有测试函数都在固件作用范围内:
$ pytest --setup-show tests/fixture/test_scope.py::TestClassScope
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 2 itemstests\fixture\test_scope.pySETUP C class_scopetests/fixture/test_scope.py::TestClassScope::()::test_1 (fixtures used: class_scope).tests/fixture/test_scope.py::TestClassScope::()::test_2 (fixtures used: class_scope).TEARDOWN C class_scope========================== 2 passed in 0.03 seconds ===========================
四、自动执行
目前为止,所有固件的使用都是手动指定,或者作为参数,或者使用 usefixtures
。
如果我们想让固件自动执行,可以在定义时指定 autouse
参数。
下面是两个自动计时固件,一个用于统计每个函数运行时间(function
作用域),一个用于计算测试总耗时(session
作用域):
# test_autouse.pyDATE_FORMAT = '%Y-%m-%d %H:%M:%S'@pytest.fixture(scope='session', autouse=True)
def timer_session_scope():start = time.time()print('\nstart: {}'.format(time.strftime(DATE_FORMAT, time.localtime(start))))yieldfinished = time.time()print('finished: {}'.format(time.strftime(DATE_FORMAT, time.localtime(finished))))print('Total time cost: {:.3f}s'.format(finished - start))@pytest.fixture(autouse=True)
def timer_function_scope():start = time.time()yieldprint(' Time cost: {:.3f}s'.format(time.time() - start))
注意下面的两个测试函数并都没有显式使用固件:
def test_1():time.sleep(1)def test_2():time.sleep(2)
执行测试可看到,固件自动执行并完成计时任务:
$ pytest -s tests/fixture/test_autouse.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 2 itemstests\fixture\test_autouse.py
start: 2018-06-12 10:16:27
. Time cost: 1.003s.
. Time cost: 2.003s.
finished: 2018-06-12 10:16:30
Total time cost: 3.016s.========================== 2 passed in 3.11 seconds ===========================
五、重命名
固件的名称默认为定义时的函数名,如果不想使用默认,可以通过 name
选项指定名称:
# test_rename.py@pytest.fixture(name='age')
def calculate_average_age():return 28def test_age(age):assert age == 28
六、参数化
在“第二部分 测试函数”中,介绍了函数的参数化测试:
# test-function/test_parametrize.py@pytest.mark.parametrize('passwd',['123456','abcdefdfs','as52345fasdf4'])
def test_passwd_length(passwd):assert len(passwd) >= 8
因为固件也是函数,我们同样可以对固件进行参数化。在什么情况下需要对固件参数化?
假设现在有一批 API 需要测试对不同数据库的支持情况(对所有数据库进行相同操作),最简单的方法就是针对每个数据库编写一个测试用例,但这包含大量重复代码,如数据库的连接、关闭,查询等。
进一步,可以使用固件抽离出数据库的通用操作,每个 API 都能复用这些数据库固件,同时可维护性也得到提升。
更进一步,可以继续将这些固件合并为一个,而通过参数控制连接到不同的数据库。这就需要使用固件参数化来实现。固件参数化需要使用 pytest 内置的固件 request
,并通过 request.param
获取参数。
@pytest.fixture(params=[('redis', '6379'),('elasticsearch', '9200')
])
def param(request):return request.param@pytest.fixture(autouse=True)
def db(param):print('\nSucceed to connect %s:%s' % param)yieldprint('\nSucceed to close %s:%s' % param)def test_api():assert 1 == 1
执行结果:
$ pytest -s tests/fixture/test_parametrize.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 2 itemstests\fixture\test_parametrize.py
Succeed to connect redis:6379
.
Succeed to close redis:6379Succeed to connect elasticsearch:9200
.
Succeed to close elasticsearch:9200========================== 2 passed in 0.10 seconds ===========================
注解
与函数参数化使用 @pytest.mark.parametrize
不同,固件在定义时使用 params
参数进行参数化。
固件参数化依赖于内置固件 request
及其属性 param
。
七、内置固件
tmpdir & tmpdir_factory
用于临时文件和目录管理,默认会在测试结束时删除。
注解
tmpdir
只有 function
作用域,只能在函数内使用。
使用 tmpdir.mkdir()
创建目临时录,tmpdir.join()
创建临时文件(或者使用创建的目录)。
def test_tmpdir(tmpdir):a_dir = tmpdir.mkdir('mytmpdir')a_file = a_dir.join('tmpfile.txt')a_file.write('hello, pytest!')assert a_file.read() == 'hello, pytest!'
注解
tmpdir_factory
可以在所有作用域使用,包括 function, class, module, session
。
@pytest.fixture(scope='module')
def my_tmpdir_factory(tmpdir_factory):a_dir = tmpdir_factory.mktemp('mytmpdir')a_file = a_dir.join('tmpfile.txt')a_file.write('hello, pytest!')return a_file
pytestconfig
使用 pytestconfig
,可以很方便的读取命令行参数和配置文件。
下面示例演示命令行参数解析:首先在 conftest.py
中使用函数 pytest_addoption
(特定的 hook function ):
# conftest.pydef pytest_addoption(parser):parser.addoption('--host', action='store',help='host of db')parser.addoption('--port', action='store', default='8888',help='port of db')
然后就可以在测试函数中通过 pytestconfig
获取命令行参数:
# test_config.pydef test_option1(pytestconfig):print('host: %s' % pytestconfig.getoption('host'))print('port: %s' % pytestconfig.getoption('port'))
注解
pytestconfig
其实是 request.config
的快捷方式,所以也可以自定义固件实现命令行参数读取。
# conftest.pydef pytest_addoption(parser):parser.addoption('--host', action='store',help='host of db')parser.addoption('--port', action='store', default='8888',help='port of db')@pytest.fixture
def config(request):return request.config# test_config.pydef test_option2(config):print('host: %s' % config.getoption('host'))print('port: %s' % config.getoption('port'))
执行结果:
$ pytest -s --host=localhost tests/fixture/test_config.py::test_option2
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.6.1, py-1.5.2, pluggy-0.6.0
rootdir: F:\self-repo\learning-pytest, inifile:
collected 1 itemtests\fixture\test_config.py host: localhost
port: 8888
.========================== 1 passed in 0.06 seconds ===========================
capsys
capsys
用于捕获 stdout
和 stderr
的内容,并临时关闭系统输出。
# test_capsys.pydef ping(output):print('Pong...', file=output)def test_stdout(capsys):ping(sys.stdout)out, err = capsys.readouterr()assert out == 'Pong...\n'assert err == ''def test_stderr(capsys):ping(sys.stderr)out, err = capsys.readouterr()assert out == ''assert err == 'Pong...\n'
monkeypatch
monkeypath
用于运行时动态修改类或模块。
小技巧
In Python, the term monkey patch only refers to dynamic modifications of a class or module at runtime, motivated by the intent to patch existing third-party code as a workaround to a bug or feature which does not act as you desire.
一个简单的 monkeypatch 如:
from SomeOtherProduct.SomeModule import SomeClassdef speak(self):return "ook ook eee eee eee!"SomeClass.speak = speak
Pytest 内置 monkeypatch
提供的函数有:
setattr(target, name, value, raising=True)
,设置属性;delattr(target, name, raising=True)
,删除属性;setitem(dic, name, value)
,字典添加元素;delitem(dic, name, raising=True)
,字典删除元素;setenv(name, value, prepend=None)
,设置环境变量;delenv(name, raising=True)
,删除环境变量;syspath_prepend(path)
,添加系统路径;chdir(path)
,切换目录。
其中 raising
用于通知 pytest 在元素不存在时是否抛出异常;prepend
如果设置,环境变量将变为 value+prepend+<old value>
。
下面使用保存配置文件示例说明 monkeypatch 的作用和使用。
假设我们需要切换某个服务到国内科大源以加速,有以下脚本用于修改配置文件 .conf.json
:
# test_monkeypatch.pydef dump_config(config):path = os.path.expanduser('~/.conf.json')with open(path, 'w', encoding='utf-8') as wr:json.dump(config, wr, indent=4)def test_config():dump_config(config)path = os.path.expanduser('~/.conf.json')expected = json.load(open(path, 'r', encoding='utf-8'))assert expected == config
似乎测试正常执行完全没有问题,但如果我们的家目录下恰好有这个配置文件并且维护了许多配置,运行测试将会覆盖原有配置,这太可怕了!
所以我们需要修改测试,最好能在临时目录里完成。但程序已经写死了文件路径,怎么办?
这种在运行时控制程序的功能就需要 monkeypatch 来实现,下面在测试过程中修改了环境变量:
# test_monkeypatch.pydef test_config_monkeypatch(tmpdir, monkeypatch):monkeypatch.setenv('HOME', tmpdir.mkdir('home'))dump_config(config)path = os.path.expanduser('~/.conf.json')expected = json.load(open(path, 'r', encoding='utf-8'))assert expected == config
现在测试会来临时目录中执行,但环境变量可能对系统有依赖,所以更好的解决方法能自己控制路径中 ~
的替换,这次通过改变 os.path.expanduser
的行为来实现:
# test_monkeypatch.pydef test_config_monkeypatch2(tmpdir, monkeypatch):fake_home = tmpdir.mkdir('home')monkeypatch.setattr(os.path, 'expanduser',lambda x: x.replace('~', str(fake_home)))dump_config(config)path = os.path.expanduser('~/.conf.json')expected = json.load(open(path, 'r', encoding='utf-8'))assert expected == config
recwarn
recwarn
用于捕获程序中 warnings
产生的警告。
# test_recwarn.pydef warn():warnings.warn('Deprecated function', DeprecationWarning)def test_warn(recwarn):warn()assert len(recwarn) == 1w = recwarn.pop()assert w.category == DeprecationWarning
此外,pytest 可以使用 pytest.warns()
捕获警告:
def test_warn2():with pytest.warns(None) as warnings:warn()assert len(warnings) == 1w = warnings.pop()assert w.category == DeprecationWarning
Pytest 基础教程相关推荐
- [转]Pytest 基础教程
文章目录 第一部分:快速入门 一.快速入门 1. 安装 Pytest 2. 第一个测试函数 3. 运行测试函数 4. 测试失败 第二部分:测试函数 一.断言 二.捕获异常 三.标记函数 1.Pytes ...
- Pytest基础之强大的mark使用教程
pytest基础使用 Pytest是一个易用.强大.灵活的功能测试框架,并且兼容unittes的测试用例 易用:用例编写简单,断言方便 强大:全能的mark.强大的fixtures 灵活:灵活的执行控 ...
- Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现
自Spring Cloud Alibaba发布第一个Release以来,就备受国内开发者的高度关注.虽然Spring Cloud Alibaba还没能纳入Spring Cloud的主版本管理中,但是凭 ...
- [SQL基础教程] 1-5 表的删除和更新
[SQL基础教程] 1-5 表的删除和更新 表的删除 语法 DROP TABLE <表名>; 法则 1-12 删除的表无法恢复 表定义的更新 语法 ALTER TABLE<表名> ...
- python 包用法_Python 基础教程之包和类的用法
Python 基础教程之包和类的用法 这篇文章主要介绍了 Python 基础教程之包和类的用法的相关资料, 需要的朋友可以参考下 Python 是一种面向对象.解释型计算机程序设计语言,由 Guido ...
- Spring Cloud Alibaba 基础教程:Nacos 生产级版本 0.8.0
Spring Cloud Alibaba 基础教程:Nacos 生产级版本 0.8.0 昨晚Nacos社区发布了第一个生产级版本:0.8.0.由于该版本除了Bug修复之外,还提供了几个生产管理非常重要 ...
- Python培训基础教程都教哪些
根据相关数据统计,目前学习Python技术的同学大多数是零基础,都是从其他行业转型来学习的,那么Python培训基础教程都教哪些呢?好不好学呢?来看看下面的详细介绍. Python培训基础教程都教哪些 ...
- 深度学习之Pytorch基础教程!
↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:李祖贤,Datawhale高校群成员,深圳大学 随着深度学习的发展 ...
- python平稳性检验_时间序列预测基础教程系列(14)_如何判断时间序列数据是否是平稳的(Python)...
时间序列预测基础教程系列(14)_如何判断时间序列数据是否是平稳的(Python) 发布时间:2019-01-10 00:02, 浏览次数:620 , 标签: Python 导读: 本文介绍了数据平稳 ...
最新文章
- 函数式编程语言时代已经来临
- 计算机视觉开源库OpenCV之图像翻转
- python dict函数用法_如何将python中的dict作为参数传入c函数中用c做相关的处理?...
- 高德地图-轨迹回放(二)
- 信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言——1080:余数相同问题
- LSGO软件技术团队2015~2016学年第十三周(1123~1129)总结
- Solaris 11 安装图解(8)
- dj电商-富文本编辑器
- 【转】Tag的创建和组织
- SAP License:标准层次无法创建
- SAP License:SAP中的报表利器Report Painter
- Satwe楼板能用弹性模计算吗_SATWE软件计算结果分析
- ISO50001认证辅导,ISO 50001的能源管理允许组织对自己内部的节能潜力进行系统开发
- 2019年注册测绘师备考历程
- 小白必看!DIY装机,电脑显示器选购
- 华为RH2288 V3服务器新加硬盘不识别
- Android安全:禁止APP录屏和截屏
- php实现对ppt的编辑,在powerpoint中用于对幻灯片内容进行编辑的视图是什么?
- 深入理解JVM4:内存结构篇(方法区)
- html5 三国杀,OL还更新HTML5吗?不更新不充值了