【原文链接】
一、pytest执行txt文件格式的文档测试
pytest 命令本身就可以直接直接执行test*.txt格式的文档测试脚本,文档测试脚本内容默认就是python解释器中额内容,如下:
test_demo01.txt
>>> 1+1
2
>>> "a"*3
'aaa'
>>> 1+1
3
执行结果如下,第二个1+1的结果被手动修改为了3,所以导致了期望结果为3,实际计算结果为2,这就是文档测试
$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo01.txt F
=============================================================================== FAILURES ===============================================================================
______________________________________________________________________ [doctest] test_demo01.txt _______________________________________________________________________
001 >>> 1+1
002 2
003 >>> "a"*3
004 'aaa'
005 >>> 1+1
Expected:
3
Got:
2
D:\src\blog\tests\test_demo01.txt:5: DocTestFailure
======================================================================= short test summary info ========================================================================
FAILED test_demo01.txt::test_demo01.txt
========================================================================== 1 failed in 0.03s ===========================================================================
二、修改默认的文档测试文件格式
可以通过–doctest-glob参数指定文本文件格式,如下
test_demo01.log文件,内容如下:
>>> 1+1
2
>>> "a"*3
'aaa'
执行结果如下:
$ pytest -s --doctest-glob="*.log"
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo01.log .
========================================================================== 1 passed in 0.03s ===========================================================================
三、执行类或者函数中的测试文本内容
在类或者函数的字符串注释中可以加入文本测试的内容,这些测试文本其实就是将python解释器交互环境中的内容,使用pytest执行的时候加上–doctest-modules参数即可执行这些文本测试,如下:
demo.py内容如下,即写了求两个数之和的函数,同时在注释中加入了个交互式测试内容
def add_two(a,b):
"""
add two elem
:param a:
:param b:
:return: a+b
>>> add_two(1,10)
11
>>> add_two("a","b")
'ab'
>>> add_two([1,2,3],[5,6,7])
[1, 2, 3, 5, 6, 7]
"""
return a+b
执行结果如下,可以发现用例通过了
$ pytest --doctest-modules
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
demo.py . [100%]
========================================================================== 1 passed in 2.21s ===========================================================================
若想默认的pytest命令就执行文本测试,则只需要在pytest.ini中增加如下内容即可
[pytest]
addopts = --doctest-modules
此时执行结果如下:
$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
demo.py . [100%]
========================================================================== 1 passed in 2.57s ===========================================================================
四、文档测试常用的选项参数
文档测试中可以通过doctest_optionflags指定一些常用的选项参数
(1)NORMALIZE_WHITESPACE参数,忽略前后的空白字符
test_demo.txt内容如下:
>>> print(111)
111
使用pytest执行结果如下:
$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.txt .
========================================================================== 1 passed in 0.03s ===========================================================================
然后把test_demo.txt修改为如下,即在结果前面增加了几个空格
>>> print(111)
111
再次执行
$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.txt F [100%]
=============================================================================== FAILURES ===============================================================================
_______________________________________________________________________ [doctest] test_demo.txt ________________________________________________________________________
001 >>> print(111)
Expected:
111
Got:
111
D:\src\blog\tests\test_demo.txt:1: DocTestFailure
======================================================================= short test summary info ========================================================================
FAILED test_demo.txt::test_demo.txt
========================================================================== 1 failed in 0.03s ===========================================================================
此时当在pytest.ini文件中增加如下配置:
[pytest]
doctest_optionflags = NORMALIZE_WHITESPACE
再次执行,即可以哦谈过了
$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.txt . [100%]
========================================================================== 1 passed in 0.03s ===========================================================================
(2)IGNORE_EXCEPTION_DETAIL参数
test_demo.txt内容如下:
>>> 1/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
执行结果如下:
$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.txt . [100%]
========================================================================== 1 passed in 0.03s ===========================================================================
当把测试内容修改如下,即只保留第一行和最后一行,此时就需要使用IGNORE_EXCEPTION_DETAIL参数了
>>> 1/0
Traceback (most recent call last):
ZeroDivisionError: division by zero
在pytest.ini配置文件中使用如下配置:
[pytest]
doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL
如此,才能使测试通过
$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.txt . [100%]
========================================================================== 1 passed in 0.03s ===========================================================================
(3)NUMBER 参数,当NUMBER 激活后,对于浮点数数值,匹配结果只要匹配到跟期望值相同的位置时一样就算通过了
如 pytest.ini配置如下:
[pytest]
doctest_optionflags = NUMBER
在交互式解释器中计算math.pi结果如下:
>>> import math
>>> math.pi
3.141592653589793
test_demo.txt内容如下:
>>> import math
>>> math.pi
3.14
执行pytest的结果如下,是可以通过的
$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.txt . [100%]
========================================================================== 1 passed in 0.03s ===========================================================================
此时把pytest.ini中的配置删掉,再次执行,结果如下,发现此时是失败的
$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.txt F [100%]
=============================================================================== FAILURES ===============================================================================
_______________________________________________________________________ [doctest] test_demo.txt ________________________________________________________________________
001 >>> import math
002 >>> math.pi
Expected:
3.14
Got:
3.141592653589793
D:\src\blog\tests\test_demo.txt:2: DocTestFailure
======================================================================= short test summary info ========================================================================
FAILED test_demo.txt::test_demo.txt
========================================================================== 1 failed in 0.03s ===========================================================================
(4)其他常用参数
- ALLOW_UNICODE 默认将字符串前的u去掉
- ALLOW_BYTES 默认将字符串前的b去掉
五、文档测试失败后继续执行
首先如下脚本,test_demo.txt内容,即第一个1+2期望结果是0显然是失败的
>>> 1+2
0
>>> 2+3
4
执行结果如下,可以发现当第一条失败后,后面的2+3则没有执行,这样的处理逻辑在有时候是不合适的,比如有100个用例,本意是想看看100个能有多少个鞥通过,结果遇到失败后面的就不执行了,这显然不合理,此时就需要失败继续执行的操作
$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.txt F [100%]
=============================================================================== FAILURES ===============================================================================
_______________________________________________________________________ [doctest] test_demo.txt ________________________________________________________________________
001 >>> 1+2
Expected:
0
Got:
3
D:\src\blog\tests\test_demo.txt:1: DocTestFailure
======================================================================= short test summary info ========================================================================
FAILED test_demo.txt::test_demo.txt
========================================================================== 1 failed in 0.03s ===========================================================================
为失败继续执行,使用–doctest-continue-on-failure参数即可,结果如下,可以看出,第二个也拨错了,说明第一个失败后,第二个也执行了
$ pytest --doctest-continue-on-failure
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.txt F [100%]
=============================================================================== FAILURES ===============================================================================
_______________________________________________________________________ [doctest] test_demo.txt ________________________________________________________________________
001 >>> 1+2
Expected:
0
Got:
3
D:\src\blog\tests\test_demo.txt:1: DocTestFailure
001 >>> 1+2
002 0
003 >>> 2+3
Expected:
4
Got:
5
D:\src\blog\tests\test_demo.txt:3: DocTestFailure
======================================================================= short test summary info ========================================================================
FAILED test_demo.txt::test_demo.txt
========================================================================== 1 failed in 0.03s ===========================================================================
若让pytest默认执行此策略,则可以在pytest.ini中按照如下设置:
[pytest]
addopts = --doctest-continue-on-failure
六、设置失败报告输出格式类型
如下,为文档测试几种报告输出格式,有一些细微差别,可自行选择
pytest --doctest-report none
pytest --doctest-report udiff
pytest --doctest-report cdiff
pytest --doctest-report ndiff
pytest --doctest-report only_first_failure
七、文档测试中调用fixture
使用getfixure函数,参数填写fixture名称即可获取到该fixture的对象,如下
test_demo.py代码如下:
import pytest
@pytest.fixture()
def get_ten():
return 10
def add_two(a,b):
"""
>>> ten=getfixture("get_ten")
>>> ten
10
"""
return a+b
执行结果如下:
$ pytest --doctest-modules
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.py . [100%]
========================================================================== 1 passed in 3.40s ===========================================================================
八、如何使用doctest_namespace
doctest_namespace简单点说可以定义一些变量使用,如下便是一个例子
conftest.py中写一个自动加载的fixture
import pytest
import numpy
@pytest.fixture(autouse=True)
def add_np(doctest_namespace):
doctest_namespace["np"] = numpy
test_demo.py编写如下函数,其中有文档测试
def arange():
"""
>>> a = np.arange(10)
>>> len(a)
10
"""
pass
执行结果如下,即在conftest.py中想其中写入np对应的值,然后再测试文件的文档测试中可以直接使用np变量
$ pytest --doctest-modules
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.py . [100%]
========================================================================== 1 passed in 4.22s ===========================================================================
九、文档测试中的skip的应用
如下,在测试步骤后面加上‘# doctest: +SKIP’的注释即可跳过
test_demo.py代码如下:
def test_random():
"""
>>> 1+2 # doctest: +SKIP
0
>>> 1 + 1
2
"""
执行结果如下:
$ pytest --doctest-modules
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 2 items
test_demo.py .. [100%]
========================================================================== 2 passed in 2.39s ===========================================================================
为了验证上述结论正确性,将上述跳过的注释去掉,即代码如下:
def test_random():
"""
>>> 1+2
0
>>> 1 + 1
2
"""
执行结果如下;
$ pytest --doctest-modules
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 2 items
test_demo.py F. [100%]
=============================================================================== FAILURES ===============================================================================
___________________________________________________________________ [doctest] test_demo.test_random ____________________________________________________________________
002
003 >>> 1+2
Expected:
0
Got:
3
D:\src\blog\tests\test_demo.py:3: DocTestFailure
======================================================================= short test summary info ========================================================================
FAILED test_demo.py::test_demo.test_random
===================================================================== 1 failed, 1 passed in 3.78s ======================================================================
也可以直接在注释文档中使用pytest.skip()函数,这里需要注意的是,如果在py文件即模块的函数或者类的注释文档中,pytest.skip()只能跳过当前注释文档段,不会影响当前文件其他文档注释,如下:
demo_demo.py
def test_demo01():
"""
>>> import pytest
>>> a=10
>>> if a>0:
... pytest.skip()
>>> 1 + 1
3
"""
pass
def test_demo02():
"""
>>> 1 + 1
3
"""
pass
执行结果如下:
$ pytest --doctest-modules
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 4 items
test_demo.py sF.. [100%]
=============================================================================== FAILURES ===============================================================================
___________________________________________________________________ [doctest] test_demo.test_demo02 ____________________________________________________________________
013
014 >>> 1 + 1
Expected:
3
Got:
2
D:\src\blog\tests\test_demo.py:14: DocTestFailure
======================================================================= short test summary info ========================================================================
FAILED test_demo.py::test_demo.test_demo02
================================================================ 1 failed, 2 passed, 1 skipped in 6.38s ================================================================
这里需要注意,当在txt文件中使用了skip,则skip后面的所有步骤都忽略
test_demo.txt内容如下:
>>> import pytest
>>> pytest.skip()
>>> 1+2
0
>>> 2+3
1
>>> 2+4
2
执行结果如下,可以看出,剩余的其他都跳过了
$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item
test_demo.txt s [100%]
========================================================================== 1 skipped in 0.03s ==========================================================================