Python pytest 测试框架

Python测试相关库

  • unittest:内置库,模仿PyUnit写的,简洁易用,缺点是比较繁琐。unittest+HTMLTestRunner。
  • nose:测试发现,发现并运行测试。
  • pytest:目前喜欢用这个,写起来很方便,并且很多知名开源项目在用,推荐。
  • mock:替换掉网络调用或者 rpc 请求等

Python 鄙视链:pytest 鄙视 > unittest 鄙视 > robotframework

pytest 是 Python 的第三方单元测试框架,比自带 unittest 更简洁和高效,支持315种以上的插件,同时兼容 unittest 框架。这就使得我们在 unittest 框架迁移到 pytest 框架的时候不需要重写代码

全功能Python测试框架:pytest

官网:

pytest 是一个非常成熟的全功能的 Python 测试框架,主要有以下几个特点:

  • 简单灵活,容易上手。
  • 支持参数化。
  • 能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/appnium等自动化测试、接口自动化测试(pytest+requests)。
  • pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如pytest-selenium(集成selenium)、pytest-html(完美html测试报告生成)、pytest-rerunfailures(失败case重复执行)、pytest-xdist(多CPU分发)等。
  • 测试用例的 skip 和 xfail 处理。
  • 可以很好的和jenkins集成。
  • report 框架—-allure 也支持了 pytest。
  • pytest 可以和 allure生成非常美观的测试报告。
  • pytest 可以实现测试用例的跳过以及reruns失败用例重试。

安装pytest

1
2
3
4
pip install -U pytest

# 验证安装的版本:
pytest --version

几个pytest documentation中的例子

例子1

1
2
3
4
5
6
7
import pytest

# content of test_sample.py
def func(x):
return x + 1
def test_answer():
assert func(3) == 5

命令行切换到文件所在目录,执行测试(也可以直接在IDE中运行):

这个测试返回一个失败报告,因为func(3)不返回5。

例子2

当需要编写多个测试样例的时候,我们可以将其放到一个测试类当中,如:

1
2
3
4
5
6
7
8
class TestClass:  
def test_one(self):
x = "this"
assert 'h' in x

def test_two(self):
x = "hello"
assert hasattr(x, 'check')

运行以上例子,该测试共执行了两个测试样例,一个失败一个成功。同样,我们也看到失败样例的详细信息,和执行过程中的中间结果。-q即-quiet,作用是减少冗长,具体就是不再展示pytest的版本信息。

pytest 执行测试需要遵行的规则

  • 所有的单测文件名都需要满足 test_*.py 格式或 *_test.py 格式。
  • 在单测文件中,测试类以 Test 开头,并且不能带有 init 方法(注意:定义class时,需要以T开头,不然pytest是不会去运行该class的)
  • 在单测类中,可以包含一个或多个 test_ 开头的函数。
  • 此时,在执行pytest命令时,会自动从当前目录及子目录中寻找符合上述约束的测试函数来执行。
  • 断言使用基本的 assert 即可。

运行方式

1
2
3
4
5
6
7
8
9
10
11
12
import pytest


def test_a():
print("------->test_a")
assert 1 # 断言成功
def test_b():
print("------->test_b")
assert 0 # 断言失败

if __name__ == '__main__':
pytest.main("-s test_abc.py") # 调用pytest的main函数执行测试

1.测试类主函数模式

main()括号内可传入执行参数和插件参数,通过[]进行分割,[]内的多个参数通过‘逗号’进行分割。

运行目录及子包下的所有用例 pytest.main(['目录名'])
运行指定模块所有用例 pytest.main(['test_reg.py'])
运行指定模块指定类指定用例 pytest.main(['test_reg.py::TestClass::test_method']) 冒号分割

1
pytest.main("-s  test_abc.py")
  1. 命令行模式
1
2
3
pytest 文件路径/测试文件名
# 例如
pytest ./test_abc.py

Pytest Exit Code 含义清单

  • Exit code 0 所有用例执行完毕,全部通过
  • Exit code 1 所有用例执行完毕,存在Failed的测试用例
  • Exit code 2 用户中断了测试的执行
  • Exit code 3 测试执行过程发生了内部错误
  • Exit code 4 pytest 命令行使用错误
  • Exit code 5 未采集到可用测试用例文件

运行模式

Pytest 的多种运行模式,让测试和调试变得更加得心应手,下面介绍5种常用的模式。

在介绍之前需要提醒一句,运行pytest时会找当前目录及其子目录中的所有 test_*.py*_test.py 格式的文件以及以test开头的方法或者class,不然就会提示找不到可以运行的case了。

1.运行指定的case

当我们写了较多的cases时,如果每次都要全部运行一遍,无疑是很浪费时间的,通过指定case来运行就很方便了。

  1. 单独执行某一个py文件里所有的用例

    1
    pytest test.py
  2. 执行目录下所有用例

    1
    pytest testcase/
  3. 单独执行某个用例,通过 node id 指定测试用例. node id由模块文件名、分隔符、类名、方法名、参数构成,举例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    # 以函数形式的用例
    pytest test_login.py::test_1

    # 运行类
    pytest test_login.py::TestClass

    # 以类形式的用例
    pytest test_login.py::TestClass::test_1

    注意:定义class时,需要以T开头,不然pytest是不会去运行该class的。

  4. 通过标记表达式执行

    1
    pytest -m slow

    这条命令会执行被装饰器 @pytest.mark.slow 装饰的所有测试用例

  5. 通过包执行测试

    1
    pytest --pyargs pkg.testing

    这条命令会自动导入包 pkg.testing,并使用该包所在的目录,执行下面的用例。

2.运行后生成测试报告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 生成txt格式报告:
pytest --resultlog=path/log.txt

# 生成XML格式的报告:
pytest --junitxml=path/log.xml

# 将测试报告发送到pastebin服务器,执行下面的命令会生成报告的网址
pytest test_report.py --pastebin=all

# 只发送失败的报告
py.test test_report.py --pastebin=failed

# 生成Html格式报告
# 这个需要安装pytest的第三方插件pytest-html:
pip install pytest-html
py.test test_report.py --html=path/log.html

在报告中可以清晰的看到测试结果和错误原因,定位问题很容易。

3.多进程运行cases

当cases量很多时,运行时间也会变的很长,如果想缩短脚本运行的时长,就可以用多进程来运行。

安装pytest-xdist:

1
pip install -U pytest-xdist

运行模式:

1
pytest test_se.py -n NUM

其中NUM填写并发的进程数。

4.重试运行cases

在做接口测试时,有事会遇到503或短时的网络波动,导致case运行失败,而这并非是我们期望的结果,此时可以就可以通过重试运行cases的方式来解决。

安装pytest-rerunfailures:

1
pip install -U pytest-rerunfailures

4.1 使用装饰器 @pytest.mark.flaky(reruns=5, reruns_delay=2)

  • reruns :最大重试次数
  • reruns_delay :重试间隔时间,单位是秒
1
2
3
4
5
6
7
8
9
10
# coding=utf-8
import pytest


@pytest.mark.flaky(reruns=5, reruns_delay=2)
def test():
assert 0==1

if __name__ =="__main__":
pytest.main(['test_sample.py','-s'])
4.2 使用命令行
1
pytest test_se.py --reruns NUM --reruns-delay NUM

参数与装饰器 @pytest.mark.flaky 一致

5.显示print内容

在运行测试脚本时,为了调试或打印一些内容,我们会在代码中加一些print内容,但是在运行pytest时,这些内容不会显示出来。如果带上-s,就可以显示了。

运行模式:

1
pytest test_se.py -s

另外,pytest的多种运行模式是可以叠加执行的,比如说,你想同时运行4个进程,又想打印出print的内容。可以用:

1
pytest test_se.py -s -n 4

6.pytest 按顺序 执行测试

如果不想按顺序执行:order = 1 第一个执行 通过mark标记 则改变默认执行顺序

1
2
3
@pytest.mark.run(order=1)
def test_03(self)
pass

7.分组执行 (冒烟等)

smoke:冒烟测试用例,分布在各个模块中,可以分模块分组等。使用mark

第一步:给不同组别的测试用例增加mark.指定名称,名称随便指定

1
2
3
@pytest.mark.smoke
def test_03(self)
pass

第二步:给pytest.ini中增加markers=指定名称:备注

1
2
3
4
[pytest]
...
markers=
smoke:冒烟用例

第三步:通过 -m “smoke” 执行有 smoke 标记的test方法

1
2
3
4
5
6
7
8
pytest  -v -m "smoke" pytest1.py

# 通过 -m "not smoke" 执行没有 smoke 标记的test方法
pytest -v -m "not smoke" pytest1.py

# 删选多个标签
pytest -v -m "smoke and test" pytest1.py
pytest -v -m "smoke or test" pytest1.py

pytest参数

1、-K EXPRESSION:执行某个关键字的用例。
用例要匹配给出的表达式;使用python的语法,匹配的范围是文件名、类名、函数名为变量,用and来区分

1
2
3
4
5
6
7
8
9
10
# content of test.py
class TestClass(object):
def test_zne(self):
x = "this"
assert 'h' in x
def test_two(self):
x = "hello"
assert hasattr(x, 'check')
def test_a(self):
assert 1==2
运行pytest时带-k参数
1
pytest -k "test and TestClass and not test_a"  test.py
可以看出,test_a这个用例被取消选择了,没有运行了
  1. –maxfail=num:当错误个数到达给定数时,退出测试,这里就不列举实例了,结果与-x类似

  2. -m MARKEXPR:只能运行有相应标识的测试用例,使用这个参数,测试用例要使用@pytest.mark.marker修饰

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # content of test.py
    import pytest
    class TestClass(object):
    def test_one(self):
    '''new_etests'''
    x = "this"
    assert 'h' in x

    @pytest.mark.slow
    def test_two(self):
    '''new_sssetests'''
    x = "hello"
    assert hasattr(x, 'check')

    def test_a(self):
    assert 1==2

    teste_two使用了@pytest.mark.slow来修饰

    在使用时,使用如下参数

    1
    pytest –m slow test.py

    从结果可以看出,只运行了一个我们带有标识的用例。

    注意,-m后面不能带’’号(单引号),只能带“”(双引号),不然识别不到

    如果要运行多个标识的话,用表达式,如下

    1
    2
    3
    pytest -m "slow or faster"   运行有slow标识或 faster标识用例
    pytest -m "slow and faster" 运行有slow和faster标识的用例
    pytest -m "slow and not faster" 运行有slow和没有faster标识的用例
  3. -v, –verbose:详细结果

  4. -q, –quiet:极简结果显示,简化控制台的输出,可以看出输出信息和之前不添加-q不信息不一样, 下图中有两个..点代替了pass结果

  5. -s:输入我们用例中的调式信息,比如print的打印信息等,我们在用例中加上一句 print(driver.title),我们再运行一下我们的用例看看,调试信息输出

  6. -V:可以输出用例更加详细的执行信息,比如用例所在的文件及用例名称等

  7. –junit-xml=path:输出xml文件格式,在与jenkins做集成时使用

  8. –result-log=path:将最后的结果保存到本地文件中

## Pytest配置文件

pytest的配置文件通常放在测试目录下,名称为pytest.ini,命令行运行时会使用该配置文件中的配置.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[pytest]
addopts = -s # 空格分隔,可添加多个命令行参数 -所有参数均为插件包的参数配置测试搜索的路径
testpaths = ./scripts # 当前目录下的scripts文件夹 -可自定义

# 配置测试搜索的文件名称
# 当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件 -可自定义配置测试搜索的测试类名
python_files =
test*.py
test_*.py
#当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件中,以Test开头的类 -可自定义配置测试搜索的测试函数名
python_classes =
Test_*
Test*
#当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件中,以Test开头的类内,以test_开头的方法 -可自定义
python_functions = test_*

Python pytest 测试框架
https://flepeng.github.io/021-Python-32-框架-pytest-Python-pytest-测试框架/
作者
Lepeng
发布于
2021年4月27日
许可协议