pytest
pytest
pytest 是一个成熟、全套的 python 自动化测试工具,旨在帮助你写出更好的程序。它可以用来做单元测试,也可以用来做功能测试、接口自动化测试;相比 unittest,它能支持更多、更全面的功能,有着以下特色和优势。
直接使用纯粹的 python 语言, 不需要你过多学习框架特定的语法,例如 self.assert* 等,以此减少学习成本;
- pytest 框架不需要写诸如 setUp()、tearDown() 这样的方法,它可以直接开始测试;
- pytest 可以自动识别测试用例,无须像 unittest 一样将测试用例放进 TestSuite 里组装;
- test fixtures 包括数据参数化测试非常好用;
- pytest 支持错误重试;
- pytest 支持并发测试。
pytest安装
pytest 不是 python 标准库,故使用时需要安装:
1 | 安装 |
pytest使用
pytest简单使用
如下Python脚本:
1 | class TestSample(object): |
在这个测试文件(test.py)里,我定义了一个测试类 TestSample,然后在这个测试类下面定义了两个测试用例,分别是 test_equal 和 test_not_equal,下面来运行下这个测试类:
pytest 直接运行 unittest 测试用例
pytest 兼容 unittest,原来使用 unittest 框架写的代码,可以被 pytest 直接调用。
在创建tests/test_sample.py
1 | import unittest |
在运行时,我们可以直接用如下方式调用:
如果你想运行整个测试用例集,或者你想把之前用 unittest 的测试用例全部换成用 pytest 执行,该如何操作呢?
我们把 main.py 文件里,所有关于执行测试用例的部分,从 unittest 执行更改为 pytest 执行,更改后的 main.py 函数如下
1 | import glob |
然后在命令行下执行:
正因为 pytest 完全兼容 unittest,以及具备刚刚介绍的那些诸多优点,才使得 pytest 风靡于整个 python 社区。
pytest 查找测试用例的原则
指定命令行参数时的查找原则
如果指定了命令行参数,则根据命令行参数执行。
这句话很好理解,像我们之前的测试里,均指定了测试要执行的 module(例如 “python -m pytest tests/test_sample.py”),故 pytest 只会查找 test_sample.py 文件。
未指定命令行参数时的查找原则
如果未指定命令行参数(即直接在命令行输入 pytest),则从 testpath(已配置)或从当前目录开始查找可用的测试用例, 其步骤如下:
- 搜索由任何符合以下规则的文件
test_*.py
或*_test.py
文件。 - 找到后,从这些文件中,收集如下测试项:test 为前缀的函数;Test 为前缀的类里面的以 test 为前缀的函数。
举个例子来理解下这个原则,看如下的工程结构:
注意:我有一个测试文件为 sample.py,里面包括两个测试用例,然后我 tests 文件夹下有两个 .py 文件共计 4 个测试用例,如果我在命令行里输入以下命令执行及结果:
仅仅有 4 个测试用例运行了,但不包括 sample.py 里的两个测试用例,这就是 pytest 默认查找在起作用,因为运行 pytest 时,我没有指定运行某个文件,所以 pytest 自动在当前目录下查找以“test_”开头或者以“_test” 结尾的 py 文件,显然sample.py 不符合这个规则,故被忽略了。
如果直接在命令行运行sample.py:
执行看到 sample.py 里的两个测试方法都被执行了(因为指定了运行文件)。
接下来,更改 sample.py 为 test_sample.py,然后把这个文件里的方法“test_equal”改成“equal_test”,于是项目文件结构如下:
在命令运行及结果:
共有 5 个测试被执行,其中不包括 equal_test 方法,因为它不是以 test 开头。
pytest运行方式详解及其参数
pytest 有两个测试运行方式:
- 命令行运行
- pytest.main() 运行
命令行运行
pytest 支持在命令行中以如下方式运行:
1 | python -m pytest [...] |
pytest.main() 运行
除了命令行运行方式外,pytest 还支持在程序中运行,在程序中运行的命令如下:
1 | pytest.main([...]) |
不管是使用命令行运行或者使用 pytest.main() 的方式运行,它们支持的参数都是一样的。需要注意的是:pytest 的参数必须放在一个 list 或者 tuple 里。
pytest 参数
pytest 支持特别多的参数,具体有哪些参数可以通过如下命令查看:
1 | pytest --help |
常用参数:
-m: 用表达式指定多个标记名。
pytest 提供了一个装饰器 @pytest.mark.xxx,用于标记测试并分组(xxx是你定义的分组名),以便你快速选中并运行,各个分组直接用 and、or 来分割。
-v: 运行时输出更详细的用例执行信息
不使用 -v 参数,运行时不会显示运行的具体测试用例名称;使用 -v 参数,会在 console 里打印出具体哪条测试用例被运行。
-q: 类似 unittest 里的 verbosity,用来简化运行输出信息。
使用 -q 运行测试用例,仅仅显示很简单的运行信息, 例如:
1 | .s.. [100%] |
-k: 可以通过表达式运行指定的测试用例。
它是一种模糊匹配,用 and 或 or 区分各个关键字,匹配范围有文件名、类名、函数名。
-x: 出现一条测试用例失败就退出测试。
在调试时,这个功能非常有用。当出现测试失败时,停止运行后续的测试。
运行指定文件夹下的测试用例
pytest 支持更简单的方法实现指定文件夹运行:
1 | 执行所有当前文件夹及子文件夹下的所有测试用例 |
除此之外,pytest 还允许你通过更复杂的方式来挑选测试用例执行。例如,我们可以通过 -m 或者 -k 的参数,把我们的测试用例限制在某一个文件夹下,这样就实现了仅允许指定文件夹下的测试用例。
选择测试用例执行
pytest 里选择测试用例执行有很多方法,可以按照测试文件夹、测试文件、测试类和测试方法四种。
按照测试文件夹执行
- “运行指定文件夹下的测试用例”中已说明
按照测试文件执行
1
2运行test_sample.py下的所有的测试用例
pytest test_sample.py按照测试类执行
按照测试类执行,必须以如下格式:
pytest 文件名 .py:: 测试类,其中“::”是分隔符,用于分割测试 module 和测试类。
1
2运行test_sample.py文件下的,类名是TestSample下的所有测试用例
pytest test_sample.py::TestSample按照测试方法执行
同样的测试方法执行,必须以如下格式:
pytest 文件名 .py:: 测试类 :: 测试方法,其中 “::” 是分隔符,用于分割测试 module、测试类,以及测试方法。
1
2运行test_sample.py文件下的,类名是TestSample下的,名字为test_equal的测试用例
pytest test_sample.py::TestSample::test_equal不在命令行执行,在程序中执行
以上选择测试用例执行的方法,可以不在命令行,而直接在测试程序里执行,其语法为pytest.main([模块.py::类::方法])
动态挑选测试用例运行 — 按 Tag
动态挑选测试用例一直是测试框架的刚需,在 pytest 里动态挑选测试用例需要借助两个步骤。
首先给测试用例打标签(mark),在 Class、method 上加上如下装饰器:
1
在运行时,命令行动态指定标签运行:
1
2
3
4
5
6同时选中带有这两个标签的所有测试用例运行
pytest -m "mark1 and mark2"
选中带有mark1的测试用例,不运行mark2的测试用例
pytest -m "mark1 and not mark2"
选中带有mark1或 mark2标签的所有测试用例
pytest -m "mark1 or mark2"下面来实际演示下,我们更改 tests 文件夹下的两个文件,其中对 test_baidu.py 文件的修改如下:
对test_sample.py文件修改如下:
给这两个测试类分别加上了标签 baidu 和标签sample,现在我们按需运行及查看结果:
可以看到baidu下面的两条用例执行了,但是sample下面的两条用例没有执行,在 Console 中显示 “2 deselected”。
注意,查看上面的执行结果有警告,也给定了官方地址说明解决问题,有两种解决方式(二选一):
添加pytest.ini配置文件,指定标签名
1
2
3[pytest]
markers = baidu
sample添加pytest_configure.py配置类(该方式未生效,不明所以)
1
2
3
4
5# 单个标签
def pytest_configure(config):
config.addinivalue_line(
"markers", "baidu"
)1
2
3
4
5
6
7# 多个标签
def pytest_configure(config):
marker_list = ["baidu", "sample"]
for marker in marker_list:
config.addinivalue_line(
"markers", marker
)
动态挑选测试用例运行 — 按名称
pytest 中,动态挑选测试用例,除了打标签(mark)外,还有另外一种方式:
1 | -k 参数是按照文件名、类名、方法名来模糊匹配的 |
下面进行详细的演示,项目结构如下:
1 | |--test_framework |
test_baidu.py 里定义了一个测试类 Baidu, 这个测试类下有两个测试方法 test_baidu_search 和 test_baidu_set;
test_sample.py 中定义了一个测试类 TestSample,这个测试类下面有两个测试方法 test_equal和 test_not_equal。
在命令行中以如下方式运行。
按照文件名称全匹配:
1
2运行test_baidu.py下的所有的测试
pytest -k "test_baidu.py"
按照文件名字部分匹配:
1
2因为baidu能匹配上test_baidu.py,故运行test_baidu.py下所有的测试
pytest -k "baidu"按照类名匹配:
1
2因为Baidu能匹配上test_baidu.py里定义的测试类Baidu,故运行Baidu测试类下所有的测试。 你也可以写成Bai
pytest -k "Baidu"按照方法名匹配:
1
2equal能匹配test_sample.py中定义的测试类TestSample下的测试方法test_equal和test_not_equal, 这两个方法都会执行
pytest -k "equal"
忽略测试用例执行
有挑选测试用例执行,那么就一定会有忽略测试用例执行,忽略测试用例执行有如下 3 种方式:
直接忽略测试执行
- 直接忽略可以使用 @pytest.mark.skip 装饰器来实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14import pytest
# 在TestSample这个类上加标签sample
class TestSample(object):
# 测试用例默认以test开头
def test_equal(self):
assert 1 == 1
def test_not_equal(self):
assert 1 != 0以上示例,在TestSample里定义的测试方法test_equal,然后加上装饰器pytest.mark.skip,那么在执行测试时test_equal会被忽略
按条件忽略测试执行 — 使用 skipif 忽略
- 按 skipif 条件,当条件符合时便会忽略某条测试用例执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import pytest
# 在TestSample这个类上加标签sample
class TestSample(object):
# 定义一个flag,用来指示是否要skip一个测试用例
flag = 1
# 此处判断flag的值,为1则忽略,否则则不忽略
def test_equal(self):
assert 1 == 1
def test_not_equal(self):
assert 1 != 0执行用例时,test_equal会被忽略
更改flag的值为非1时,再次执行用例,该方法将会被执行。
按条件忽略测试执行 — 使用 -m 或者 -k 忽略
- 除了 skip 和 skipif 外,我们也可以通过 -m 或者 -k 的方式,把我们不需要运行的测试用例给过滤掉,从而实现测试用例的忽略执行。
setUp 和 tearDown 详解
1. 按 module 进行 setup 和 tear down
按 module 进行 setup 和 tear down,即在某一个 module 内 setup 或者 tear down 的方法只会执行一次,pytest 里用于 module 的 set up 和 tear down 方法为:
1 | def setup_module(module): |
需注意以下几个事情:
- setup_module(module) 和 teardown_module(module) 的写法最好不要改动;
- 当 setup_module 出错,teardown_module 不会被执行;
- 一个 module(.py 文件)可以包括多个 Class,多个classs 下可能有多个 case,但是 setup_module 和 teardown_module 只会执行一次。
2. 按 class 进行 setup 和 tear down
在某一个测试类内,同样可以进行 set up 和 tear down。
1 | class Baidu(object): |
需注意以下几个事情:
setup_class(cls) 和 teardown_class(cls) 的写法最好不要改动。
setup_class(cls) 和 teardown_class(cls) 必须以 @classmethod 装饰。
当 setup_class(cls) 出错,teardown_class(cls) 不会被执行。
3.按 method 进行 setup 和 tear down
针对每一个测试用例,同样可以进行 set up 和 tear down。
1 | def setup_method(self, method): |
需注意以下两个事情:
setup_method(self, method) 和 teardown_method(self, method) 的写法最好不要改动。
当 setup_method(self, method)用例执行失败时,teardown_method(self, method) 不会被执行。
setup 和 teardown 在我们测试开始和结束后准备/清理测试数据,系统状态时非常有用。
4.使用 pytest.ini 文件破除默认 pattern,灵活命名测试文件
pytest 查找测试用例,会根据测试用例名,仅默认查找前缀以 _tes开头或者后缀以 _test 结尾的测试文件;而查找测试方法,仅查找测试类以 Test 开头,测试方法以 test 开头。
那有没有办法破除这一依赖呢?当然有,那就是使用 pytest.ini 文件。
pytest.ini 是 pytest 的主配置文件,可以改变 pytest 的默认行为。在项目根目录 lagoutAPITest 下新创建一个文件 pytest.ini,我们的项目结构就变成这样:
1 | |--test_framework |
pytest.ini 的内容如下:
1 | [pytest] |
- python_classes,表示要匹配的测试类的 pattern,*匹配所有。
- python_files,表示要匹配的测试文件。
- python_functions,表示要匹配的测试方法。
在这里我把 python_files 和 python_classes 均设置为*,表示任何 *.py 都将被认为是测试文件。任何名字的测试类都将被认为是测试类。
新建一个sample.py的测试文件,内容如下:
1 | class Sample(object): |
定义了一个 Sample 类,其中包括两个测试方法 test_equal 和 not_equal。然后我切换到项目根目录下,在命令行运行及结果:
可以看到,sample.py 这个文件被当作了测试文件,并且 Sample 这个类也被 pytest 视为测试类,因为他们符合 pytest.ini 里的配置。
而 Sample 类的两个测试方法中,test_equal 被执行,而 not_equal 没有被执行,因为我在 pytest.ini 里关于 python_functions 的配置是必须以 test 开头。
使用 pytest.ini 可以针对 pytest 做更多配置,做法如下所示。
注册标签
1
2
3
4[pytest]
markers =
baidu
sample可以在 pytest.ini 里注册标签,并统一管理。
在 pytest.ini 文件里注册标签并不意味着你不需要在测试类/测试方法里忽略 @pytest.mark.xxx 装饰器,你仍需要在你的测试用例上加上标签。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import pytest
# 在TestSample这个类上加标签sample
class TestSample(object):
# 定义一个flag,用来指示是否要skip一个测试用例
flag = 1
# 此处判断flag的值,为1则忽略,否则则不忽略
def test_equal(self):
assert 1 == 1
def test_not_equal(self):
assert 1 != 0
指定测试目录
1
2[pytest]
testpaths=tests此方法把 testpaths 指定为 tests,当你在命令行中直接执行 pytest 时,会自动查询 testpaths 文件夹,如果没有设置 testpaths,则会自动从当前文件夹找起。
除了以上这些,pytest.ini 还有其他很多用处,例如更改默认命令行参数、指定 pytest 最低版本号、指定忽略某些目录、禁用 XPASS 等功能等。
5. 失败重跑测试用例
在测试执行中,偶尔会出现由于环境不稳定,或者网络不稳定造成的测试运行失败的情况,如果第一次运行就报错,那么势必会增加我们排查的工作量。
所以 pytest 支持错误失败重跑,使用失败重跑机制的步骤如下:
安装
1
pip install -U pytest-rerunfailures
命令行执行失败重跑次数
1
2语法:
--reruns Num。 其中Num是重跑的次数下面我们来实际看一个 re-run 的例子,我更改 sample.py 文件如下:
1 | class Sample(object): |
命令执行及结果:
执行后观察测试输出,会发现输出信息“1 failed,2 rerun”:
6. 并发运行测试用例集
当你的测试用例比较多时,最好可以通过并发测试来减少测试整体的运行时间。pytest 支持并发测试,并且有不同的并发测试库,其中如下两个比较著名:
pytest-parallel
安装
1 | pip install pytest-parallel |
运行:使用 pytest-parallel 运行,需要指定参数。
1 | –workers (optional) X |
多进程运行, X 是进程数,默认值 1。
1 | –tests-per-worker (optional) X |
多线程运行, X 是每个 worker 运行的最大并发线程数, 默认值1。
注意:这个插件仅仅支持 python 3.6 版本及以上,而且如果你想多进程并发,必须跑在 Unix 或者 Mac 机器上,windows 环境仅仅支持多线程运行。
运行命令如下:
1 | pytest --workers 2 #指定2个进程并发 |
pytest-xdist
安装
1 | pip install pytest-xdist |
运行
1 | 语法: |
从理论上来说,pytest-parallel 要更好一些,因为 pytest-xdist 有以下缺点:
- 非线程安全
- 多线程时性能不佳
- 需要状态隔离
但是实际应用中,pytest-parallel 有时会出现如下运行错误:
1 | BrokenPipeError: [WinError 109] 管道已结束 |
而且这个错误发生的原因不确定,官方暂时没有修复, 如果你在测试中发现这个错误,那么可以使用 pytest-xdist 来进行并发测试。
pytest 集成测试报告
pytest-html
安装
1
pip install pytest-html
使用
1
pytest --html=report.html
演示
报告
执行完成后会在项目的根目录下生成一个report.html文件
浏览器打开report.html文件
pytest-html 还支持错误重试,使用如下命令运行
1 | pytest --html=report.html --self-contained-html --reruns 2 |
运行结束你会发现,错误的测试用例被运行了 2 次。
allure
安装:在不同操作系统上安装 allure 的步骤是不同的
MacOS
1
2
3
4
5安装
brew install allure
查看安装版本
allure --versionLinux
1
2
3sudo apt-add-repository ppa:qameta/allure
sudo apt-get update
sudo apt-get install allureWIndows
在 Windows 上安装 allure,首先要安装 Scoop,Scoop 的安装步骤如下:
1
2
3
4
5
6
7
8
9
10以Win10为例:
1. 使用快捷键 Win + R 调起运行提示框
2. 输入“cmd”进入到命令行
3. 输入“powershell”进入到powershell模式(此时你的命令提示应该以PS开始)
4. 确保你的PowerShell版本大于5.0,命令如下:
psversiontable.psversion.major # 这个运行后出现的值应该>=5.0
5. 允许PowerShell 执行本地脚本:
set-executionpolicy remotesigned -scope currentuser
6. 安装Scoop
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')安装好 Scoop 后,不要关闭 powershell,直接输入如下命令安装 allure:
1
PS C:\Users\Admin>scoop install allure
如果你之前安装过 allure,也可以通过如下方式更新:
1
PS C:\Users\Admin>scoop update allure
查看当前使用的 allure 版本:
1
PS C:\Users\Admin>allure --version
本地安装完成allure,还需Python安装allure的模块
1
pip install allure-pytest
执行 pytest 命令,并指定 allure 报告目录
可以直接在命令行里执行
1
pytest --alluredir ./report/allure_raw
在程序里执行
1
2pytest.main(["-m", "smoke",
"--alluredir=./allure_reports"])
执行完成后,在当前目录下,report目录会生成一个allure_raw的原始文件,这个只是测试报告的原始文件,不能打开成html的报告
打开html的报告需要启动allure服务,启动命令如下
1
allure serve report/allure_raw
测试报告如下: