Pytest简介
测试技术对python应用开发不仅重要,而且占据了不少工作量。pytest是一个容易入手的测试框架, 并且支持复杂的功能测试,是python内置unittest模块、nose 模块的不错的替代选项。
pytest官网文档
什么是unit test ?
Unit test(单元测试)是软件测试的第一步,通常用于在开发过程中,测试函数、类、API接口等软件基础构件的功能。 自动化的单元测试, 是根据 test plan 用脚本写成测试用例(test case) , 自动执行,当 test case 较多,且重复测试次数多,自动化单元测试的效率要远高于人工测试。
并且自动化测试工作还可以自动保存日志并生成测试报告,方便进行测试结果分析。
下面展示了如何使用 pytest 模块测试 Python 应用
pytest 安装
使用以下命令安装 Pytest:
$ pip install pytest
这将安装pytest库。
pytest 测试文件默认搜索方式
如果未指定任何参数,则在testpaths(如果已配置)或当前目录中的位置搜索测试文件。 另外,命令行参数可以在目录,文件名或节点 ID 的任何组合中使用。
pytest 在所选目录中查找test_*.py或*test.py文件。 在选定的文件中,pytest 在类之外查找带前缀的测试函数,并在带前缀的测试类中查找带前缀的测试方法(无__init_()方法)。
运行 pytest
pytest 不带任何参数,将查看当前工作目录(或其他一些预配置的目录)以及测试文件的所有子目录,并运行找到的测试代码。
$ pytest
运行当前目录中的所有测试文件。
$ pytest min_max_test.py
我们可以通过指定名称作为参数来运行特定的测试文件。
$ pytest min_max_test.py::test_min
可以通过在::字符后提供其名称来运行特定功能。
$ pytest -m smoke
标记可用于对测试进行分组。 然后使用pytest -m运行一组标记的测试。
$ pytest -k <expression>
另外,我们可以使用表达式来运行与测试函数和类的名称匹配的测试。
Python pytest 简单示例
在第一个示例中,我们将使用 pytest 测试两个简单的数学算法。
algo.py
defmax(values):
_max = values[0]for val in values:if val > _max:
_max = val
return _max
defmin(values):
_min = values[0]for val in values:if val < _min:
_min = val
return _min
我们有一个带有自定义max()和min()功能的模块。
min_max_test.py
#!/usr/bin/env python3import algo
deftest_min():
values =(2,3,1,4,6)
val = algo.min(values)assert val ==1deftest_max():
values =(2,3,1,4,6)
val = algo.max(values)assert val ==6
测试文件
min_max_test.py
的名称中包含一个测试词。
deftest_min():
values =(2,3,1,4,6)
val = algo.min(values)assert val ==1
此外,测试功能test_min()具有测试字。 我们使用assert关键字来测试算法的值。
$ pytest min_max_test.py
================================================ test session starts =================================================
platform win32 -- Python 3.9.6, pytest-7.1.3, pluggy-1.0.0-- D:\APP\python\python.exe
cachedir:.pytest_cache
rootdir: D:\workplace\python\test1
collected 2 items
pytest_test01.py::test_min PASSED [50%]
pytest_test01.py::test_max PASSED [100%]=================================================2 passed in0.22s ==================================================
这是输出。 有两个测试,并且都成功通过了。 pytest -v min_max_test.py显示了更详细的输出。
如果把 test_max()方法改为如下, 结果会怎样
deftest_max():
values =(2,3,1,4,6)
val = algo.max(values)assert val ==7
执行结果报错,并给出详细的错误位置
Pytest 跳过
使用跳过装饰器,我们可以跳过指定的测试。 跳过测试有多种原因。 例如,数据库/在线服务目前不可用,或者我们跳过了 Windows 上针对 Linux 的特定测试。
skipping.py
#!/usr/bin/env python3import algo
import pytest
@pytest.mark.skipdeftest_min():
values =(2,3,1,4,6)
val = algo.min(values)assert val ==1deftest_max():
values =(2,3,1,4,6)
val = algo.max(values)assert val ==6
在示例中,test_min()被跳过。
$ pytest min_max_test.py
================================================= test session starts =================================================
platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\Jano\Documents\pyprogs\pytest
collected 2 items
min_max_test.py s. [100%]
========================================= 1 passed, 1 skipped in 0.04 seconds =========================================
在测试文件名后面的输出中,s 代表跳过的和。 通过。
pytest 标记
我们可以使用标记将测试组织为单元。
marking.py
#!/usr/bin/env python3# pytest -m a marking.py# pytest -m b marking.pyimport pytest
@pytest.mark.adeftest_a1():assert(1)==(1)@pytest.mark.adeftest_a2():assert(1,2)==(1,2)@pytest.mark.adeftest_a3():assert(1,2,3)==(1,2,3)@pytest.mark.bdeftest_b1():assert"falcon"=="fal"+"con"@pytest.mark.bdeftest_b2():assert"falcon"==f"fal{'con'}"
我们有两组由标记 a 和 b 标识的测试。 这些单元由
pytest -m a marking.py
和
pytest -m b marking.py
运行。
Pytest 参数化测试
通过参数化测试,我们可以向断言中添加多个值。 我们使用@pytest.mark.parametrize标记。
parametrized.py
#!/usr/bin/env python3import algo
import pytest
@pytest.mark.parametrize("data, expected",[((2,3,1,4,6),1),((5,-2,0,9,12),-2),((200,100,0,300,400),0)])deftest_min(data, expected):
val = algo.min(data)assert val == expected
@pytest.mark.parametrize("data, expected",[((2,3,1,4,6),6),((5,-2,0,9,12),12),((200,100,0,300,400),400)])deftest_max(data, expected):
val = algo.max(data)assert val == expected
在示例中,我们使用多个输入数据测试这两个功能。
@pytest.mark.parametrize("data, expected",[((2,3,1,4,6),1),((5,-2,0,9,12),-2),((200,100,0,300,400),0)])deftest_min(data, expected):
val = algo.min(data)assert val == expected
我们将两个值传递给测试函数:数据和期望值。 在我们的例子中,我们用三个数据元组测试min()函数。
$ pytest parametrized.py
================================================= test session starts =================================================
platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\Jano\Documents\pyprogs\pytest
collected 6 items
parametrized.py ......[100%]==============================================6 passed in0.03 seconds ===============================================
Pytest 输出告知有六次运行。
pytest fixture 夹具
测试需要在一组已知对象的背景下进行。 这组对象称为测试夹具。
algo.py
defsel_sort(data):ifnotisinstance(data,list):
vals =list(data)else:
vals = data
size =len(vals)for i inrange(0, size):for j inrange(i+1, size):if vals[j]< vals[i]:
_min = vals[j]
vals[j]= vals[i]
vals[i]= _min
return vals
对于此示例,我们向algo.py模块添加了一个选择排序算法。
fixtures.py
#!/usr/bin/env python3import algo
import pytest
@pytest.fixturedefdata():return[3,2,1,5,-3,2,0,-2,11,9]deftest_sel_sort(data):
sorted_vals = algo.sel_sort(data)assert sorted_vals ==sorted(data)
我们用夹具测试选择排序。
@pytest.fixturedefdata():return[3,2,1,5,-3,2,0,-2,11,9]
我们的测试装置仅返回一些测试数据。 请注意,我们通过其名称引用此灯具:data。
deftest_sel_sort(data):
sorted_vals = algo.sel_sort(data)assert sorted_vals ==sorted(data)
在test_sel_sort()函数中,我们将数据夹具作为函数参数传递。
$ pytest fixtures.py
================================================= test session starts =================================================
platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\Jano\Documents\pyprogs\pytest
collected 1 item
fixtures.py .[100%]==============================================1 passed in0.02 seconds ===============================================
这是输出。
Pytest 布局
Python 测试可以多种方式组织。 测试可以集成在 Python 包中,也可以放在包外。
综合测试
接下来,我们展示如何在 Python 包中运行测试。
setup.py
utils
│ algo.py
│ srel.py
│ __init__.py
│
└───tests
algo_test.py
srel_test.py
__init__.py
我们有这种包装布局。 测试与软件包一起位于tests子目录中。
setup.py
#!/usr/bin/env python3from setuptools import setup, find_packages
setup(name="utils", packages=find_packages())
这是setup.py。
utils/algo.py
defsel_sort(data):ifnotisinstance(data,list):
vals =list(data)else:
vals = data
size =len(vals)for i inrange(0, size):for j inrange(i+1, size):if vals[j]< vals[i]:
_min = vals[j]
vals[j]= vals[i]
vals[i]= _min
return vals
defmax(values):
_max = values[0]for val in values:if val > _max:
_max = val
return _max
defmin(values):
_min = values[0]for val in values:if val < _min:
_min = val
return _min
这是algo.py文件。
utils/srel.py
defis_palindrome(val):return val == val[::-1]
我们还有另一个模块,其中包含一个测试单词是否为回文的功能。
tests/algo_test.py
#!/usr/bin/env python3import utils.algo
import pytest
@pytest.fixturedefdata():return[3,2,1,5,-3,2,0,-2,11,9]deftest_sel_sort(data):
sorted_vals = utils.algo.sel_sort(data)assert sorted_vals ==sorted(data)deftest_min():
values =(2,3,1,4,6)
val = utils.algo.min(values)assert val ==1deftest_max():
values =(2,3,1,4,6)
val = utils.algo.max(values)assert val ==6
这些是utils.algo模块的测试。 注意,我们使用完整的模块名称。
tests/srel_test.py
#!/usr/bin/env python3import utils.srel
import pytest
@pytest.mark.parametrize("word, expected",[('kayak',True),('civic',True),('forest',False)])deftest_palindrome(word, expected):
val = utils.srel.is_palindrome(word)assert val == expected
这是对is_palindrome()功能的测试。
utils/init.py
utils/tests/init.py
两个__init__.py文件均为空。
$ pytest --pyargs utils
================================================= test session starts =================================================
platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\Jano\Documents\pyprogs\pytest\structure
collected 6 items
utils\tests\algo_test.py ...[50%]
utils\tests\srel_test.py ...[100%]==============================================6 passed in0.06 seconds ===============================================
我们使用pytest --pyargs utils命令运行测试。
外部测试
下一个示例显示了应用源布局,其中测试未集成在包内。
setup.py
src
└───utils
│ algo.py
│ srel.py
tests
algo_test.py
srel_test.py
在这种布局中,我们在源代码树之外进行测试。 请注意,不需要__init__.py文件。
$ set PYTHONPATH=src
$ pytest
总结
上述展示了pytest 的使用步骤,下面对于编写testcase 的一些建议:
- 按模块或功能将testcase放在不同的 test_*.py 打头的文件中。
- test文件名与 testcase 方法名的命令应该采用有意义的名字,一看即明白其目的。
- test case 可以按一些划分标准标记为不同的组
- 可以用fixture来准备数据。
版权归原作者 __弯弓__ 所有, 如有侵权,请联系我们删除。