前面我们讲了unittest框架如何实现报告生成,那么在Pytest中如何实现报告生成呢?在pytest中实现报告生成我们可以使用以下四种方法,如:resultlog文件、JunitXML文件、Html文件、Allure报告等。我们将重点讲解Html和Allure两种报告的生成方式。

python接口测试:2.8 Pytest之pytest-html报告生成_测试用例

——————————————

❶ResultLog、JunitXML文件生成

——————————————

直接使用pytest运行对应的测试用例模块文件,在该文件后面添加两个参数--result-log=log需要存放的路径  --junit-xml=xml所需要保存的路径,具体实现如下:

python接口测试:2.8 Pytest之pytest-html报告生成_测试用例_02

那么就会在F盘的test目录下会生成两个文件

python接口测试:2.8 Pytest之pytest-html报告生成_测试用例_03

打开这两个文件你会发现实际不是很好分析结果,所以这两种方式大家只需要知道有这个参数即可,一般很少使用。

——————————————

❷Pytest-html模块基本使用

——————————————

如果想要生成与unittest框架一样的html报告的话,则需要引用第三方模块pytest-html模块。

安装,直接通过pycharm中settings进行安装或者pip install pytest-html。

再次强调,注意你们使用的环境,有些时候装了两套python环境,你们上面的两种安装方式不同则会处于不同的环境中被安装。

安装完成,直接使用命令pytest  --html=需要保存报告所在路径   需要执行的测试用例模块文件即可生成html报告了。

在pycharm中具体设置如下:

python接口测试:2.8 Pytest之pytest-html报告生成_测试用例_04

dos中执行方式:

python接口测试:2.8 Pytest之pytest-html报告生成_自定义_05

那么在F盘中会生成如下图所示:

python接口测试:2.8 Pytest之pytest-html报告生成_测试用例_06

打开test.html文件内容如下图:

python接口测试:2.8 Pytest之pytest-html报告生成_自定义_07

从文件夹中发现会生成两个文件,一个是文件夹里面存放的是css样式的,一个是html文件,但是如果我只想生成一个html文件的话,如何实现?

可以添加参数--self-contained-html即可。例如:如下操作

python接口测试:2.8 Pytest之pytest-html报告生成_自定义_08

最后你在F盘中会发现只有一个文件test1.html不会生成一个assets文件夹了,因为此时已经将css写入到html页面中去了。

当然,如果你不想用它自带的css样式的话,可以自己写相关的css样式,然后应用到报告中即可,添加参数--css自定义的css样式文件所在路径。

——————————————

Pytest-html报告增强

——————————————

大家看到上面的报告应该有所发现我的报告跟你们是否有所不同呢?

因为我实现了报告新增三列去除了一列,分别是Time列、Y OR N列、Description列以及去除了link列。

那么是如何实现的呢?实际大家只是分析和查看官网可以发现,具体提供了操作方式:https://github.com/pytest-dev/pytest-html

下面是官网的部分描述截图

python接口测试:2.8 Pytest之pytest-html报告生成_自定义_09

我们来分析下上面代码吧。

首先第一个函数,是实现表格的表头设计,设计了两列,分别是Description和Time。并且Time列实现了排序操作。第二个函数实现是对应表头每行值的定义,实际description指的就是在程序中的doc注释。而Time的值使用的utcnow的值,会发现与当前自己的系统时间相差八小时,因为我们处于的是东八区,utc获取的是世界协调时间。所以我们可以自己通过localtime以及strftime方法完成获取当前计算机时间并自定义格式。

具体实现的代码如下:

from datetime import datetime
from py.xml import html
import pytest
import time
#声明报告表格的表头定义
def pytest_html_results_table_header(cells):
    #insert方法的第一个参数表示是插入到表格的是第几列,第二个参数是表头名
    cells.insert(2, html.th('Description'))
    cells.insert(1, html.th('Time', class_='sortable time', col='time'))
    cells.insert(3, html.th('Y OR N'))
    cells.pop()
#实现对应表头的行的值的操作,description、实际就是我们声明的每个方法对应docstring值
def pytest_html_results_table_row(report, cells):
    cells.insert(2, html.td(report.description))
    #下面我们自定义格式时间
    cells.insert(1, html.td(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime()), class_='col-time'))
    cells.insert(3,html.td("Y"))
    cells.pop()


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()
    #__doc__属性是系统内置的,前面我们讲过,用于获取类。方法、函数的doc注释
    report.description = str(item.function.__doc__)

我们一般会将以上代码声明在conftest.py文件中,同样的还可以自定义声明测试报告的标题,具体代码如下:

def pytest_html_report_title(report):
   report.title = "TestCaseManager的测试报告"

函数名是固定的,参数也是固定的,表示传入的是report对象,调用title属性,然后可以自定义修改其值即可。

——————————————

❹Pytest-html中如何插入截图

——————————————

因为我们之前讲过,pytest是兼容unittest框架运行的,所以我们可以直接将pytest框架融入到之前的CRM项目中去,然后使用pytest-html生成测试报告,但是如果执行的用例失败时,我们要是能够将页面的截图附录到报告中的话那就非常好了。

其实pytest-html模块已经给我们提供了,并考虑到了,我们同样可以分析官网,部分截图如下

python接口测试:2.8 Pytest之pytest-html报告生成_html_10

所以我在前面一直提到要大家多看api,多看官网,一般官网都有详细具体的说明的。

首先我们来分析下pytest.runtest_makereport这个函数,是否有印象呢,前面我们增强表格的时候是不是主方法也是这个函数,所以我们现在可以整合为一个函数即可。

你会发现下面代码,声明以及前面两句代码都是一样的,只有report.descriptinotallow=str(item.function.__doc__)不存在,所以我们只要将这句代码放到最后面即可。

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()
    report.description = str(item.function.__doc__)
import pytest
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    pytest_html = item.config.pluginmanager.getplugin('html')
    outcome = yield
    report = outcome.get_result()
    extra = getattr(report, 'extra', [])
    if report.when == 'call':
        # always add url to report
        extra.append(pytest_html.extras.url('http://www.example.com/'))
        xfail = hasattr(report, 'wasxfail')
        if (report.skipped and xfail) or (report.failed and not xfail):
            # only add additional html on failure
            extra.append(pytest_html.extras.html('<div>Additional HTML</div>'))
        report.extra = extra

所以整合之后我们代码会如下:

import pytest
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    pytest_html = item.config.pluginmanager.getplugin('html')
    outcome = yield
    report = outcome.get_result()
    extra = getattr(report, 'extra', [])
    if report.when == 'call':
        # always add url to report
        extra.append(pytest_html.extras.url('http://www.example.com/'))
        xfail = hasattr(report, 'wasxfail')
        if (report.skipped and xfail) or (report.failed and not xfail):
            # only add additional html on failure
            extra.append(pytest_html.extras.html('<div>Additional HTML</div>'))
        report.extra = extra
      report.description = str(item.function.__doc__)

通过判定报告被调用或者如果调用了测试固件对象时,那么我们就可以进行判定当前这个用例执行到底是成功的还是失败的,如果是失败的我们就需要进行截图操作,截图后,进行写入到html页面中即可。具体实现代码如下:

import pytest
import time
from CrmTest.Business_Object.Login.Login_Business import LoginBusiness
login=None
@pytest.fixture()
def get_login():
    global login
    login=LoginBusiness("http://123.57.71.195:7878/index.php/login")
    # print("这是setup方法")
    #return login
    yield login   #如果针对被测试模块中的测试用例需要实现调用teardown方法的话,那么自定义固件对象中可以通过yield的关键字替代return语句以完成teardown的操作
    # print("这是teardown方法")
    time.sleep(2)
    login.get_driver.quit()


#缺少驱动器对象
def get_image(filename):
    print("这是外面的conftest的驱动器对象",get_login)
    login.get_driver.save_screenshot(filename)


from CrmTest.GET_PATH import GETPATH
import os
#真正完成扩展模块扩展内容写入到报告中的主方法
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    pytest_html = item.config.pluginmanager.getplugin('html')
    outcome = yield
    report = outcome.get_result()
    extra = getattr(report, 'extra', [])
    if report.when == 'call' or report.when == "setup":    #判定需要获取失败的状态,如果运行失败的话则进行页面截图并写入报告操作,内置方法__call__如果被显式声明的话则对象可以直接通过方法调用
        xfail = hasattr(report, 'wasxfail')
        if (report.skipped and xfail) or (report.failed and not xfail):
            #需要实现截图操作;图片宽和高的大小的单位是像素,如果点击图片则可以实现显示当前图片
            #可以专门设定一个目录完成报告以及图片的存放
            get_dir=os.path.join(GETPATH,"Report_Object/ErrorImage")
            if not(os.path.exists(get_dir)):
                os.mkdir(get_dir)
            #图片的名字不能够固定,如果固定,如果多个失败则只有一张图片,图片的名字如何定义,最好图片能够对应测试用例的名字,用例的名字是可以取到的
              #获取每个测试用例具体名字,但是文件命名不能够存在::特殊符号
            get_testcase_name=report.nodeid.split("::")[-1]
            get_index=get_testcase_name.find("[")
            get_last_index=get_testcase_name.find("]")
            get_name=get_testcase_name[get_index+1:get_last_index]
            get_name=get_name.split("-")
            get_new_name=""
            for value in get_name:
                if "\\" in value:
                    value=value.encode("utf-8").decode("unicode_escape")
                get_new_name+=value+"_"
            print("分割数据名称",get_new_name)   #"_".join(get_name)
            filename=get_new_name+".png"
            print("图片的文件名",filename)
            filepath=os.path.join(get_dir,filename)
            get_image(filepath)
            extra.append(pytest_html.extras.html('<div><img src="../Report_Object/ErrorImage/%s" style="width:400px;height:400px;" notallow="window.open(this.src)" align="right" ></div>'%filename))
        report.extra = extra
    report.description = str(item.function.__doc__)

这里需要注意的是:

首先,这些代码都应该声明在conftest.py文件中。

其次,我们截图必然会要求获取到固件对象实现驱动器初始化操作,从而实现截图操作,所以我们现在将get_login固件对象声明在这里。

再者,需要声明一个截图的函数,在判定如果用例执行失败时则执行截图并保存操作,在这里我截图的图片名称做了额外的处理,直接引用测试用例的名称进行显示,测试用例名可以通过报告中的nodeid属性进行获取。

获取到的名会如下格式:

python接口测试:2.8 Pytest之pytest-html报告生成_html_11

所以我们有需要进行处理,处理过程我相信各位应该能够看懂吧,这看不懂就不要思考后面的问题了~~~

在这里我要强调的是,我们的项目结构如下:

python接口测试:2.8 Pytest之pytest-html报告生成_测试用例_12

大家首先应该理解了conftest.py的作用域问题了吧,登陆中的conftest是属于login包下的,无法作用到外面来,而最外面的conftest是父包下的,可以作用域任何一个子包下不存在对应固件对象的情况下调用。

那么问题来了,我们前面将所有的代码都声明在父包下,我们应该将get_login的固件对象声明在Login包下的,如果声明在父包下的,那么新增用户呢?到时候也得声明这里了,那么太乱了,所以我们还是应该将各自对象声明在各自包下,报告生成的函数就全部声明在父包下的conftest中。但是又有问题了,声明在Login包中后,那么父包下的conftest生成报告中需要调用截图函数,那么驱动器对象怎么能够从最里层包中传出来呢?大家可以思考下哦~~~