Ch6 unit testing
摘要
基本用法
pip install pytest
保持目录以及文件名上的一致,使代码库更易读。例如,mylib/foobar.py
的测试文件应为 mylib/tests/test_foobar.py
。pytest 会自动识别所有以 test_
开头的 python 文件以及文件中所有以 test_
开头的函数。
- 使用
pytest -v test_true.py
运行指定文件测试,-v
为给出详细测试结果。若以文件夹为参数,pytest 会遍历文件夹并自动运行所有以test_
开头的文件。 - 使用参数
-k
运行测试文件中的指定函数,pytest -v test_true.py -k test_false
。 - 使用参数
-m
筛选标记的测试,例如,@pytest.mark.selfdefine
,之后在命令行中运行pytest -v test_true.py -m selfdefine
,也可进行反选pytest -v test_true.py -m 'not selfdefine'
。如果没有在pytest.ini
或pyproject.toml
中注册自定义的名称,则会报 warning。 - 使用参数
-n
设置并行进程数,需要安装pytest-xdist
,也可使用-n auto
自动设置进程数。 - 使用
assert
对语句进行判断。 -
使用
pytest.skip()
,@pytest.mark.skip()
和@pytest.mark.skipif()
跳过指定测试。@pytest.mark.skip("Do not run") def test_false(): assert False @pytest.mark.skipif(mylib is None, reason="mylib not avaliable") def test_mylib(): assert mylib.foobar() def test_skip_at_runtime(): if True: pytest.skip("Skip at runtime")
-
使用
@pytest.fixture
来预定义一些参数、文件、数据库等,并在测试结束后清除,这样不需要重复写测试所需初始化的代码。通过使用yield
来实现在测试函数运行结束后执行yield
后的代码。需要注意的是,fixture
会在每个测试函数运行前初始化,并在其结束后清除,如果需要重复使用多次,可指定@pytest.fixture(scope="module")
,在整个module
运行结束后才会对fixture
函数进行清除。@pytest.fixture def database(): db = <database> yield db db.close() def test_insert(database): database.insert(123)
可设置自动运行,
@pytest.fixture(autouse=True)
,会在整个module
中对每个测试函数自动运行。但注意不要滥用,可能会降低运行效率。可使用不同参数对函数进行测试,
@pytest.fixture(params=["param1","param2"])
,测试函数会自动运行两次,对两个参数分别进行测试
Mock
Python 3.3 以前的版本通过 import mock
引入,之后的版本通过 from unittest import mock
引入。
-
创建 mock 实例
from unittest import mock m = mock.Mock()
-
添加属性
m.some_attribute = "hello world" >>> m.some_attribute hello world
-
添加函数,设置返回值
m.some_method.return_value = 42 >>> m.some_method() 42 >>> m.some_method("argument") 42
-
添加函数,运行指定代码
def print_hello(): print("hello world") return 43 m.some_method.side_effect = print_hello >>> m.some_method() hello world 43 >>> m.some_method.call_count 1
call_count
可用来统计该函数调用的次数 -
检查传入参数
m.some_method("foo","bar") >>> m.some_method.assert_called_once_with('foo','bar') None >>> m.some_method.assert_called_once_with('foo',mock.ANY) None >>> m.some_method.assert_called_once_with('foo','baz') ... AssertionError: expected call not found. Expected: some_method('foo', 'baz') Actual: some_method('foo', 'bar')
-
替换函数,并运行指定代码
def fake_os_unlink(path): raise IOError("Testing") with mock.patch('os.unlink', fake_os_unlink): os.unlink('foobar')
也可使用装饰器的方式,以下两种均可
@mock.patch("os.unlink", fake_os_unlink) def test_unlink(): os.unlink("foobar") @mock.patch("os.unlink") def test_unlink(mock_unlink): # set return_value mock_unlink.return_value = False mock_unlink.side_effect = [False] assert os.unlink("foobar")
-
未被测试覆盖的代码 安装
pytest-cov
,运行pytest --cov=<packagename> path/to/unit/tests/folder
。- 设置参数
--cov-report=html
,将在 htmlcov 文件夹下生成 HTML 页面。 - 设置参数
--cov-fail-under=50
,若覆盖率小于 50%,测试将失败。
- 设置参数
虚拟环境
解决不同开发环境所需版本冲突问题
- 创建虚拟环境
python3 -m venv <venv_name>
- 激活虚拟环境
source <venv_name>/bin/activate
- 退出虚拟环境
deactivate
- 使用
tox
自动化和标准化测试。- 安装
tox
,在setup.py
所在目录下创建tox.ini
文件。 - 在
tox.ini
中设置依赖和测试
[testenv] deps=pytest commands=pytest
- 参数
-e
可选择不同的测试环境,例如,py26, py27, py36
等。 - 在
tox.ini
中设置多个测试环境,需要系统中存在对应的 Python 版本
[tox] envlist=py35,py36,pypy
- 使用
detox
可并行对不同的测试环境进行测试。 #待整理笔记
- 安装