pytest是一个可以轻松创建简单的和可弹性伸缩的测试框架,测试用例的编写简洁直观、易于阅读,只需要几分钟的时间就可以为你的应用或库创建出一个小的单元测试用例或复杂的功能测试用例。
1. 安装
在命令行输入如下指令进行安装:
1 |
$ pip install -U pytest |
检查pytest版本:
1 2 |
$ pytest --version This is pytest version 3.5.1, imported from /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/pytest.py |
2. 一个简单示例
待测试程序sample.py代码如下:
1 2 3 4 5 6 7 |
# increase by 1 def increase(x): return x + 1 # decrease by 1 def decrease(x): return x - 1 |
测试用例文件test_sample.py代码如下:
1 2 3 4 5 6 7 |
from .sample import * def test_increase(): assert increase(3) == 4 def test_decrease(): assert decrease(3) == 4 |
测试结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ pytest ============================= test session starts ============================== platform darwin -- Python 3.6.3, pytest-3.5.1, py-1.5.2, pluggy-0.6.0 rootdir: /Users/jackchen/Temp/exercises/pytest/demo, inifile: collected 2 items test_sample.py .F [100%] =================================== FAILURES =================================== ________________________________ test_decrease _________________________________ def test_decrease(): > assert decrease(3) == 4 E assert 2 == 4 E + where 2 = decrease(3) test_sample.py:7: AssertionError ====================== 1 failed, 1 passed in 0.04 seconds ====================== |
当执行 pytest 命令时,若不指定参数和配置文件,则 pytest 将从当前目测开始递归查找文件名为 test_*.py 或者 *_test.py 的python源文件,并将其内部以 test_ 开头的函数作为测试用例依次执行,并输出相应的报告文本。
3. 编写测试用例
3.1 使用assert测试函数返回值
pytest可以使用python中的 assert 来测试函数的返回值,并可指定相应的测试错误消息。
1 2 3 4 5 6 7 |
from .sample import * def test_increase(): assert increase(3) == 4 def test_decrease(): assert decrease(3) == 4, "decrease()函数测试结果与预期不符,请检查!" |
3.2 使用pytest.raises测试异常抛出信息
测试异常是否抛出,若未抛出异常则测试失败:
1 2 3 4 |
import pytest def test_zero_division(): with pytest.raises(ZeroDivisionError): 1 / 0 |
当异常抛出时,可继续测试异常信息的内容:
1 2 3 4 5 |
import pytest def test_zero_division(): with pytest.raises(ZeroDivisionError) as excinfo: 1 / 0 assert 'division by zero' in str(excinfo.value) |
3.3 使用pytest.mark装饰器修饰测试用例
1.跳过测试用例:
1 2 3 4 |
import pytest @pytest.mark.skip def test_function(): ... |
2.当满足指定条件时跳过测试用例:
1 2 3 4 5 |
import sys, pytest @pytest.mark.skipif(sys.version_info < (3,6), reason="requires python3.6") def test_function(): ... |
3.当满足指定条件时,测试用例失败:
1 2 3 4 |
import pytest @pytest.mark.xfail def test_function(): ... |
此时用例会被执行并输出 “expected to fail”错误信息,但没有堆栈回溯。pytest.mark.xfail的签名如下,可在使用时设定参数,对抛出XFAIL错误的条件进行更细致的控制。
1 |
def xfail(condition=None, *, reason=None, raises=None, run=True, strict=False) |
4.一次指定多组参数对同一个测试用例进行测试:
1 2 3 4 5 6 7 8 9 10 11 |
import pytest @pytest.mark.parametrize(("n", "expected"), [ (1, 2), pytest.param(1, 2, marks=pytest.mark.xfail), (2, 3), (3, 4), pytest.param(3, 4, marks=pytest.mark.skipif(sys.version_info >= (3, 0), reason="py2k")), ]) def test_increment(n, expected): assert n + 1 == expected |
3.4 使用fixtures
pytest.fixtures装饰器是经典的xUnit编程样式中的setup/teardown函数的升级版,可为多个测试用例提供可靠的、可重复使用的资源。
1 2 3 4 5 6 7 8 9 10 11 |
import pytest @pytest.fixture def smtp(): import smtplib return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) def test_ehlo(smtp): response, msg = smtp.ehlo() assert response == 250 assert 0 # for demo purposes |
在上例中,通过 @pytest.fixture 装饰器修饰过后的函数 smtp 就成为了一个fixture对象,当测试用例 test_ehlo 需要使用该fixture对象时,将其名字作为参数传入即可。运行时,fixture对象的执行结果将被缓存,可被声明范围内的多个测试用例所共享。fixture的默认scope为function,可选值如下表所示:
scope取值 | 作用范围说明 |
---|---|
session | 当前测试的整个会话中均可共享,超出范围后销毁 |
module | 进入模块时创建fixture对象,模块范围内可共享,超出范围后销毁 |
class | 进入class时创建fixture对象,class范围内均可共享,超出范围后销毁 |
function | 进入函数时创建fixture对象,函数作用域内可以共享,超出范围后销毁 |
fixture 的签名如下:
1 |
def fixture(scope="function", params=None, autouse=False, ids=None, name=None) |
如需显示执行销毁代码,可参照如下示例的实现:
1 2 3 4 5 6 7 8 9 |
import smtplib import pytest @pytest.fixture(scope="module") def smtp(): smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) yield smtp # provide the fixture value print("teardown smtp") smtp.close() |
3.5 测试布局
pytest支持两种常用的测试布局:在应用代码的外部进行测试 和 在应用代码内部进行测试。
3.5.1 在应用代码的外部进行测试
该种布局将测试代码放置于应用代码的外部,测试前先安装应用代码 pip install -e ,其目录结构如下:
1 2 3 4 5 6 7 8 9 |
setup.py mypkg/ __init__.py app.py view.py tests/ test_app.py test_view.py ... |
若需对同一个测试用例分模块使用不同的测试代码时,可采用如下方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
setup.py src/ mypkg/ __init__.py app.py view.py tests/ __init__.py foo/ __init__.py test_view.py bar/ __init__.py test_view.py |
3.5.2 在应用代码内部进行测试
该种方法将测试代码放置于项目内部,应用模块和测试模块可以一一对应,可以逐个模块边开发边测试,其参考布局大致如下:
1 2 3 4 5 6 7 8 9 10 |
setup.py mypkg/ __init__.py app.py view.py test/ __init__.py test_app.py test_view.py ... |