自动化工具 测试开发之路 (工具篇)----pytest and pytest-allure-adaptor

孙高飞 · 2017年02月14日 · 最后由 清水 回复于 2024年01月21日 · 6271 次阅读

背景

是的, 我这个 java 技术栈的测试人开始玩 python 了,公司产品向用户提供了 python 的 sdk,所以为了能针对 sdk 做一些东西。我这两天研究了一下 python 和 python 通用的测试框架。一开始在社区和群里问了一些小伙伴们使用的都是什么,发现大多数人用的都是 unittest+HTMLTestRunner。 可能是我被 java 界那庞大的测试生态圈惯坏了,第一天就已然受不了 unittest+ HTMLTestRunner 的这种组合。所谓工欲善其事必先利其器,我花了一点时间 google 了一下 python 界常用的一些测试框架,像 unittest2,nose,pyunit,doctest 什么的。 挨个去官网查看了一下使用方式。发现 pytest 还是比较符合我的期望的。而且我用 java 的时候就一直使用的 report 框架----allure 也支持了 pytest。 所以今天还是老规矩。入门科普,抛砖引玉,详尽的文档请参考:http://doc.pytest.org/en/latest/contents.html 以及: https://github.com/allure-framework/allure-pytest

执行方式

pytest 的执行方式非常简单, 它跟 xunit 系的框架不一样,测试类不需要继承任何 pytest 的类。 在运行 pytest 命令的时候,自然会有一套 case discover 的机制识别。只需要你的类是以也就是说你的测试类和方法可以跟普通方法是一样的。只需要你的测试文件的命名规则符合 test_*.py 或者 *_test.py。 例子如下:

class TestDataLoad():
    def test_data_load(self, me):
        source_table = None
        # 判断是否已经导入这个数据
        bank_data = [t for t in me.tables if t.name == 'test_gaofei01']
        if len(bank_data) != 0:
            source_table = bank_data.pop()
        else:
            source_table = me.new_local_table('./banking_2_23.csv', 'test_gaofei01', format_='csv',
                                              first_line_schema=False)

这个时候我们到目录下执行 py.test。你会发现已经自动发现了你的测试函数并执行。 不需要像其他框架一样继承某一个类或者在函数上使用某个装饰器。 关于用例运行的具体配置,可以参照链接:http://doc.pytest.org/en/latest/usage.html

Test Fixture

fixture 一词最早是在 xunit test patterns 看到的。我也不知道翻译过来到底该叫什么,可以认为是测试所依赖的所有东西,例如参数,测试数据,数据库连接等。 在其他测试框架中一般提供了@setup @teardown 这类的方法。python 中使用@pytest.fixture 这样一个装饰器。我们直接看一个最简单的例子。

class ProphetClientBase(object):
    @pytest.fixture(scope='session')
    @allure.step('登录')
    def me(self):
        client = ProphetClient('http://172.27.1.115:8888')
        assert client.login('Admin', '1234567'), '登录失败'
        return client.me

上面我们用了 pytest 提供的装饰器来创建我们的 fixture。 这个方法也很简单,主要负责登录并返回用户对象。 这个用户对象是大部分 case 需要用的。 所以我们把它定义为一个 fixture。在其他框架中我们用 setup method 的方式来做的,这样做有个缺点就是 setup method 无法传递数据给测试方法作为参数。但是 pytest 中我们可以像下面一样做:

def test_data_load(self, me):
    source_table = None

    # 判断是否已经导入这个数据
    bank_data = [t for t in me.tables if t.name == 'test_gaofei01']

上面我们定义测试方法的时候直接在参数中使用 me 这个参数。pytest 在运行的时候会自动的帮我们找到 me 这个 fixture 运行并将返回值赋值到 me 中。不用我们做其他的处理。 同样我们在 fixture 的定义中可以看到我们使用了 scope='session', 意思是这个 fixture 是属于 session 级别的,在一个 session 中只会运行一次。是不是有点像 testng 的 beforeTest 系列了~

after 系列

刚才说了 before 系列,现在我们说说 after 系列,也即是 teardown。 一个 fixture 有时候是需要进行销毁操作的。 其他框架使用 after 或者 teardown 方法。 但是 pytest 可以直接在 fixture 中定义这个操作。只需要使用 python 的关键字 yield 代替 return。 并在 yield 之后编写销毁操作。 举个例子,还是上面的登录的 fixture,现在我们这么写:

@pytest.fixture(scope='session')
@allure.step('登录')
def me(self):
    client = ProphetClient('http://172.27.1.115:8888')
    assert client.login('Admin', '1234567'), '登录失败'
    yield client.me
    client.me.logout()

上面我们用 yield 代替 return 后执行了 logout 操作。 这样就好像是生成器一般在所有使用这个 fixture 的测试执行结束后。运行了 logout 操作。同样 pytest 也支持 with 。。 as 大法来配合 yield。如下:

@pytest.fixture
def passwd():
    with open("/etc/passwd") as f:
        yield f.readlines()

def test_has_lines(passwd):
    assert len(passwd) >= 1
自省功能

额,我是直接按字面直接翻译了。 其实这个功能就像是 java 的反射一样,可以在 fixture 中动态获取测试模块的属性来动态的执行。直接给个例子, 我们在一个文件中定义 fixture

@pytest.fixture(scope="module")
def smtp(request):
    server = getattr(request.module, "smtpserver", "smtp.gmail.com")
    smtp = smtplib.SMTP(server)
    yield smtp
    print ("finalizing %s (%s)" % (smtp, server))
    smtp.close()

然后再另一个文件中定义测试模块

smtpserver = "mail.python.org"  # will be read by smtp fixture

def test_showhelo(smtp):
    assert 0, smtp.helo()

在上面的例子中我们在 fixture 中使用了 request 这个 pytest 提供的默认参数,它其中有一个功能是可以提供测试方法的运行环境 (我理解这个功能很像 java 反射的功能之一)。在 fixture 中我们通过 getattr 直接反射出了测试模块中一个叫 smtpserver 的属性。并使用这个属性启动 server。

参数化 fixture

我们可以参数化 fixture 并且让测试函数根据不同的 fixture 运行多次测试 (很像是数据驱动的一种实现)。例子如下:

@pytest.fixture(scope="module",
params=["smtp.gmail.com", "mail.python.org"])
def smtp(request):
    smtp = smtplib.SMTP(request.param)
    yield smtp
    print ("finalizing %s" % smtp)
    smtp.close()

很简单,直接使用 params 传入一个参数列表。然后再 fixture 中使用 request.param 使用当前的参数。 这样你会发现测试方法会使用这两个 fixture 执行两次。

使用方式

除了在测试方法中显示的使用 fixture 函数作为参数外,我们还可以让 fixture 传递参数而自动执行。只需要使用@pytest.mark.usefixtures("cleandir") 这个方式就可以了。

@user12res("cleandir")
class TestDirectoryInit:
    def test_cwd_starts_empty(self):
        assert os.listdir(os.getcwd()) == []
        with open("myfile", "w") as f:
            f.write("hello")
    def test_cwd_again_starts_empty(self):
        assert os.listdir(os.getcwd()) == []

上面的例子里在 class 的定义中使用这个装饰器。这样在运行这个 class 的测试方法前都会执行一次 fixture 方法。 好了关于 fixture 的主要就介绍这么多了。 详细的请看链接:http://doc.pytest.org/en/latest/fixture.html

测试方法的参数化

刚才说了 fixture 的使用方式,但是还是无法满足我们的日常需要,我们希望有更灵活的方式给测试方法进行参数化。例如我们日常的数据驱动的测试策略。 pytest 中同样给我们提供了这样的一个方式。看下面的例子:

import pytest
def value(xmlPath):
   # 读取参数文件的代码
  pa = [
        ("3+5", 8),
        ("2+4", 6),
        ("6*9", 54),
    ]
    return pa
@user13ize("test_input,expected", value(path))
def test_eval(test_input, expected):
    print(test_input)
    assert eval(test_input) == expected

上面是我为了实现我在 testng 中做数据驱动的功能而写的 demo。通过 parametrize 这个装饰器我们就实现了跟 testng 中的 data provider 很相似的功能。tesng 是接受一个 2 维数组,而 pytest 则是一个装有多个元祖的列表。这个我觉得可能不用多说了,知道数据驱动的同学都懂。

好了关于 pytest 我就先介绍到这吧,pytest 是个很大的框架但是使用起来很简单。我今天只介绍了我们常用的比较重要的功能。 详细的文档请参阅:http://doc.pytest.org/en/latest/contents.html

allure-pytest

下面来看看为了显示高大上的 report,我们抛弃了 HTMLTestRunner 而直接使用 allure-pytest。 我之前发过帖子专门说明了 allure 在 java 和 jenkins 上如何配置插件,详情请看:https://testerhome.com/topics/5738。 来我先安利一下效果图:

内置日志系统,展示清楚,分类方便,可以上传附件。 总之我太喜欢这个 report 框架了。使用方式也很简单,安装 pytest-allure-adaptor 这个模块后,执行测试的时候增加一个参数就好了。如下:

py.test --alluredir report

这样我们的测试报告就生成在 report 目录下了。跟 java 的标注不太一样,pytest 使用装饰器的样子大概是这样的。

step 这个装饰器就是我们页面上看到的日志功能,每调用一次这个方法,页面上就会显示这个日志。具体用法看我的帖子和 allure 的官方文档吧。

总结

好了,今天先说这么多,我初入 python 圈以后还忘大家多多指教

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 53 条回复 时间 点赞

大佬,请教个问题,pytest 多机并行(在 jenkins 上多个 slave 运行)结束后,如何生成一份完整的 allure 报告,把多设备的数据在一份 allure 报告内区分开来(现在只会显示一个 slave 的数据,其它的 salve 相同的 case 会显示在 history 中)。我用了下面的这种方式,还是不行:
@allure.suite(config.node_name)
@allure.parent_suite(config.node_name)
@allure.feature(config.node_name)
@pytest.mark.parametrize("test_step", test_steps, ids=test_ids)
def test_input_rows(test_step, request):
........
该如何在报告中显示所有的 slave 的所有 case 情况啊?

ABEE ycwdaaaa (孙高飞) 在 TesterHome 的发帖整理 中提及了此贴 01月12日 13:47



这个问题有解决的办法吗各位

allure-pytest 有没有 add environment 的方法 来增加环境变量信息显示在报告中



为啥我跑出来的 report 是 xml 文件 不是 html

徐旻 回复

看了下 allure 的init.py,其实你修改下它的init.py 文件 将包括 step 在内的 api 全部用all来写,这样应该就可以让 IDE 识别了...

@ycwdaaaaallure-pytest的说明文档中看到
Note: this plugin currently supports only Allure 1.4.x series.

但是,allure1又停止维护了。这影响使用么?

六星 回复

不需要去管他,开始我也以为这样是不可以运行的,实际上 在 pycharm 里面就是这样显示的。你先把脚本跑起来,我估计不会报错的。


装完 allure,调用不了 allure.step
@lunamagic

六星 回复

和我遇上的哪个问题一样?

@lunamagic 我也遇到和你一样的问题,你当时怎么解决的?

@Tester_web 搞定了,环境变量设置的问题。。😂

@Tester_web win7 下命令行生成报告搞定了吗?

匿名 #41 · 2017年05月10日

@lunamagic @ycwdaaaa 你们有遇到这个问题没,本来 pytest 都可以直接运行的脚本,报这个错以后,以前的脚本都跑不了了

ValueError: Plugin already registered: allure_pytest=<module 'allure.pytest_plugin' from '/usr/local/lib/python2.7/site-packages/allure/pytest_plugin.py'>
Tester_web 回复

看 allure 的报告需要启动一个服务。所以最好是集成到 jenkins 上,那么在 jenkins 上就需要安装 allure 的 allure command 这个工具。这个你可以在网上找一下教程。

请问下,pytest 这个的 allure 报告里,那个 title 怎么写成中文啊?
@ycwdaaaa

@lunamagic

@ycwdaaaa
命令无法执行的问题搞定了 现在就是怎么使用 allure 生成工具 生成报告呢 windows7 环境

提示 not found





我 pytest 版本是 2.9.0
@lunamagic 大神帮忙看看 谢谢

Tester_web 回复

感觉 pytest 都没有搞定。 你先尝试用 pytest 看是不是能收集测试用例,然后执行。
直接用命令 py.test

@ycwdaaaa yc @ycwdaaaa w @lunamagic 请问大神 我用命令 py.test --alluredir report 报这个错是什么情况


在桌面新建了个文件 test 然后 login.py 文件放入 cd 到 test 文件夹里执行命令的


这个是否就算是 allure 安装成功了?我也找到的 report 目录,但是问题还是没有解决

是不是先要安装 allure 然后才可以用 pytest-allure-adaptor

徐旻 回复

你查查怎么装这个插件吧。。。。。

徐旻 回复

py.test --alluredir report

用什么命令跑?

徐旻 回复

你先别管 IDE,你用命令行跑一下命令。 排除一下 IDE 的问题,看看是不是 allure 没装好

pytest 没有问题。我已经换了三个姿势安装了,还是不对呀。 郁闷。。

徐旻 回复

额,不知道在哪看版本,这玩意还没用熟呢

孙高飞 回复

用了 pycharm 进行安装还是不行。我用的是 pycharm community edition ,你这里呢?

徐旻 回复

降级的。 现在 pytest 的 allure 有 bug 只支持 2.9.0 以下版本

还有个问题。你是先装的 pytest3.0.6,然后在装 allprue 的时候 pytest 自动降级为 2.9.0 的。还是装的时候就是 pytest2.9.0

孙高飞 回复

t h x 我再去试试

徐旻 回复

问题 1:我是直接用 IDE 装的模块
问题 2:我记得是平级的

问题 1 你是用 pip 安装的?
问题 2 告诉我你 sit-package 下面 pytest 目录和 allpure 的目录 是平级还是 allprue 目录是 pytest 的子目录

徐旻 回复


好奇怪。。。。我这里好好的。。。。。。。


显示 allure 里找不到 step。

徐旻 回复

现在是什么错误? IDE 提示找不到那个装饰器么?

我也是 pycharm 。
安装都是用 pip 安装的。

已经查了一个下午的资料了,实在想不出来怎么回事。

我安装好的目录结构是这样的。感觉怪怪的。
你这里安装目录 allure 的目录是否在 pytest 里面。

因为官网的例子有

import pytest

def test_foo():
    with pytest.allure.step('step one'):
        # do stuff

    with pytest.allure.step('step two'):
        # do more stuff

怎么看 allure 的目录都是在 pytest 下面

徐旻 回复

奇怪啊,你用的什么 IDE,我用 pycharm 没碰见这个问题

@ycwdaaaa 我安装好了 pytest 和 pytest-allure-adaptor
import pytest
import allure
都是成功的,但是就是不能调用 allure.step
我去官网上看,有一句话是这么写的

This plugin gets automatically connected to py.test via entry point if installed.

Connecting to IDE:

pytest_plugins = 'allure.pytest_plugin',\

意思是链接到 IDE 需要进行设置,也没有写怎么设置,我猜想可能是这里的问题,不知道你是否遇见过,或者已经解决了?

默默的赞一个

赞一个,准备使用这一套.现在用 nose+ HTMLTestRunner 不舒服

#8 楼 @jamesparagon 毕竟搞了几年 java 了~~ 实现的目的相同~ 只是语法不一样了么

😳 初学 python 就开始涉猎装饰器。。。。这已经是一步跨百步了

#2 楼 @dadeshuo 我刚用 java 那会也一样,自己造了一堆轮子。。😂 😂 😂 淡定,就当学习了

#5 楼 @esuter 是的,已经有人提交了 fix 了。 就在前几天我还在 github 上看到了这个 request,就是最近几天提交的。 只是现在还没有提交进去。 我觉得就快了吧。我看了提交的代码,一共也没几行。 没道理要拖很久

使用 pytest + allure 的時候需要注意一下版本,現在的版本只支援 pytest>=2.7.3,<=2.9.0,如果使用 pytest 2.9.0 以上的話,可以看一下別人提交的 PR。不知道為什麼官方死活不 Merge

😊😊咱测试就是要这种精神

—— 来自 TesterHome 官方 安卓客户端

赞!!

官方例子也是用 pytest。。想想当年自学时手写装饰器真是太傻逼了😂

赞!!我目前在搭建的 Python 断言框架用的也是 pytest,很好用!推荐的 allure-pytest 这个不错,正缺报告类的,打算后面也要集成进来。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册