Python单元测试框架——pytest(四)


Allure介绍与安装

Allure介绍

Allure是一款轻量级的开源自动化测试报告生成框架。它支持绝大部分测试框架,比如TestNG、Junit 、pytest、unittest等。本文主要介绍pytest框架结合Allure生成美观的测试报告。

Allure安装

  1. 安装Java

    Allure是基于Java的,需要安装java1.8+,并配置环境变量

    下载地址:https://www.oracle.com/cn/java/technologies/downloads/

    安装步骤略,配置环境变量如下:

    vim .bash_profile
    
    export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents    /Home
    export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
    export PATH=.:$JAVA_HOME/bin:$PATH
    
    source .bash_profile

    在终端输入java -version确认安装配置是否成功

    $ java -version
    java version "1.8.0_221"
    Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
    Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
  2. 安装Allure

    Allure下载地址:

    https://github.com/allure-framework/allure2/releases

    或者

    https://repo1.maven.org/maven2/io/qameta/allure/allure-commandline/

    下载解压后配置环境变量:

    vim .bash_profile
    
    export Allure=/Users/laobai/software/allure-2.20.1/bin
    export PATH=$Allure:$PATH
    
    source .bash_profile

    在终端输入allure --version确认安装配置是否成功

    $ allure --version
    2.20.1
  3. 安装allure-pytest

    allure-pytest是Pytest的一个插件,通过这个插件可以生成Allure所需要的用于生成测试报告的数据。

    $ pip install allure-pytest

Allure的使用

官方的使用文档:https://docs.qameta.io/allure/#_pytest (allure支持多种语言,这里介绍的是基于pytest)

基本的生成报告方法

生成报告需要两个基本的步骤:

  1. 运行pytest命令:pytest --alluredir,生成中间结果,包括json、text格式文件
  2. 运行allure命令通过中间结果生成测试报告
    • allure serve 生成在线版本的测试报告
    • allure generate 生成本地文件版本的测试报告

示例:

test_login.py

import time
import pytest
import allure
import xlrd

def readExecl():
    datas = list()
    book = xlrd.open_workbook('./testdata/login_data.xlsx')
    sheet = book.sheet_by_index(0)
    for item in range(1, sheet.nrows):
        datas.append(sheet.row_values(item))
    return datas

@pytest.mark.parametrize('username, password, result', readExecl())
class Test_login:

    def test_login_success(self, driver, username, password, result):
        driver.find_element('xpath', '/html/body/div[6]/div/div[1]/div[2]/a[1]').click()
        driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[1]/input').send_keys(
                username)
        driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[2]/div/input').send_keys(
                password)
        driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[3]/button').click()
        time.sleep(3)
        login_text = driver.find_element('xpath', '/html/body/div[2]/div/ul[1]/div/div/em[2]').text
        time.sleep(1)
        assert result in login_text
        time.sleep(1)

conftest.py

import pytest
from config.driver_config import PLChrome
from selenium import webdriver
from selenium.webdriver.chrome.service import Service


@pytest.fixture(params=[PLChrome])
def driver(request):
    get_driver = request.param().browser
    get_driver.maximize_window()
    get_driver.implicitly_wait(10)
    get_driver.get("http://101.43.8.10/")
    yield get_driver
    get_driver.quit()

生成中间文件,其中--alluredir=./report/result/是指定中间文件生成的路径

$ pytest --alluredir=./report/result/
================================================================ test session starts ================================================================
platform darwin -- Python 3.9.12, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /Users/laobai/workspaces/testdemo02
plugins: variables-2.0.0, html-3.2.0, base-url-2.0.0, allure-pytest-2.12.0, metadata-2.0.4
collected 2 items                                                                                                                                   

testcases/test_login.py ..                                                                                                                    [100%]

================================================================ 2 passed in 20.33s =================================================================

生成在线版本的测试报告,其中./report/result/指通过哪个目录中的中间文件生成测试报告

$ allure serve ./report/result/
Generating report to temp directory...
Report successfully generated to /var/folders/_c/dc6074r10fb_tzfw0h_9qx4w0000gn/T/1031052424637446839/allure-report
Starting web server...
2022-12-25 18:48:28.298:INFO::main: Logging initialized @2782ms to org.eclipse.jetty.util.log.StdErrLog
Server started at <http://192.168.18.147:50525/>. Press <Ctrl+C> to exit

生成了测试报告后,会自动启动系统默认浏览器打开生成的测试报告

可以通过左下角的‘E’icon切换报告的展示语言,支持简体中文。

生成本地文件的测试报告,其中./report/result/指通过哪个目录中的中间文件生成测试报告,-o ./report/html/是指生成本地报告的输出目录

$ allure generate ./report/result/ -o ./report/html/
Report successfully generated to ./report/html

打开生成的报告html文件,展示测试报告内容

再次执行生成本地报告文件时,会提示文件已存在,需要增加--clean参数进行覆盖生成文件内容

$ allure generate ./report/result/ -o ./report/html/ --clean
Report successfully generated to ./report/html

也可以通过命令来启动系统默认浏览器,自动打开生成的本地html报告文件

$ allure open -h 127.0.0.1 -p 8883 ./report/html/
Starting web server...
2022-12-25 19:45:15.837:INFO::main: Logging initialized @420ms to org.eclipse.jetty.util.log.StdErrLog
Server started at <http://localhost:8883/>. Press <Ctrl+C> to exit

报告内容展示:

  1. 总览【Overview】:测试运行结果整体预览

  2. 类别 【Categories】:用于展示测试结果,默认只显示测试结果为failed/error/broken测试

  3. 测试套【Suites】:测试套件,根据所有用例的层级关系,packgae、module、class、method/function来展示用例

  4. 图表【Graphs】:测试结果图形化,包括执行状态的分布图、优先级、耗时等

  5. 时间刻度【Timeline】:展示测试用例精确的测试时序

  6. 功能【Behaviors】:根据Allure特性标记epic、feature、story等分组展示测试用例和结果

  7. 包【Packages】:按照package、module来分组展示测试用例

生成具有丰富内容的报告

Allure提供以下一些装饰器对测试用例进行详细的分类、描述及信息补充,可以用于丰富报告内容,更加直观的体现出测试的结果。

方法 详细描述
@allure.epic() 史诗,相当于module级的标签
@allure.feature() 功能模块和特性,相当于class级的标签
@allure.story() 故事,相当于method级的标签
@allure.title 用例标题
@allure.testcase() 测试用例的链接
@allure.issue() bug的链接
@allure.link() 用于定义一个需要再测试报告中展示的链接
@allure.step() 测试用例的步骤
@allure.severity() 测试用例的等级,共5个等级:BLOCKER、CRITICAL、NORMAL、MINOR、TRIVAL
@allure.description() 为测试用例添加描述信息
allure.attach 为测试用例添加附件

增加常用特性

修改test_login.py如下:

import time
import pytest
import allure
import xlrd


def readExecl():
    datas = list()
    book = xlrd.open_workbook('./testdata/login_data.xlsx')
    sheet = book.sheet_by_index(0)
    for item in range(1, sheet.nrows):
        datas.append(sheet.row_values(item))
    return datas

@pytest.mark.parametrize('username, password, result', readExecl())
class Test_login:
    @allure.description("测试登录功能的测试用例")
    @allure.epic("登录注册epic")
    @allure.feature("登录feature")
    @allure.story("登录story")
    @allure.title("登录成功title")
    @allure.tag("登录注册tag")
    @allure.link('http://101.43.8.10/', name='ShopXO商城')
    @allure.issue('https://demo16.zentao.net/bug-view-162.html', name='bug162')
    @allure.testcase('https://demo16.zentao.net/testcase-view-690-1.html', name='登录注册测试用例')
    @allure.severity(allure.severity_level.CRITICAL)
    def test_login_success(self, driver, username, password, result):
        with allure.step("点击登录按钮,跳转到登录页面"):
            driver.find_element('xpath', '/html/body/div[6]/div/div[1]/div[2]/a[1]').click()
        with allure.step("在登录页面输入用户名"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[1]/input').send_keys(
                username)
        with allure.step("在登录页面输入密码"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[2]/div/input').send_keys(
                password)
        with allure.step("点击登录按钮"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[3]/button').click()
        time.sleep(3)
        with allure.step("获取登录成功后左上角的登录名"):
            login_text = driver.find_element('xpath', '/html/body/div[2]/div/ul[1]/div/div/em[2]').text
        time.sleep(1)
        with allure.step("断言获取的登录名与预期结果是否一致"):
            assert result in login_text
        time.sleep(1)

执行测试,查看报告内容

增加附件

插入文本类附件

向allure报告中插入本地文件的语法如下:

allure.attach.file(body, name=None, attachment_type=None, extension=None)

  • body:需要显示的内容,附件文本信息的内容;
  • name:附件名称;
  • attachment_type:附件类型,可以是文本内容也可以是HTML代码等,由allure.attachment_type指定;
  • extension:附件的扩展名,可省略;

修改test_login.py增加插入文本内容附件

import time
import pytest
import allure
import xlrd

def readExecl():
    datas = list()
    book = xlrd.open_workbook('./testdata/login_data.xlsx')
    sheet = book.sheet_by_index(0)
    for item in range(1, sheet.nrows):
        datas.append(sheet.row_values(item))
    return datas

@pytest.mark.parametrize('username, password, result', readExecl())
class Test_login:
    @allure.description("测试登录功能的测试用例")
    @allure.epic("登录注册epic")
    @allure.feature("登录feature")
    @allure.story("登录story")
    @allure.title("登录成功title")
    @allure.tag("登录注册tag")
    @allure.link('http://101.43.8.10/', name='ShopXO商城')
    @allure.issue('https://demo16.zentao.net/bug-view-162.html', name='bug162')
    @allure.testcase('https://demo16.zentao.net/testcase-view-690-1.html', name='登录注册测试用例')
    @allure.severity(allure.severity_level.CRITICAL)
    def test_login_success(self, driver, username, password, result):
        with allure.step("点击登录按钮,跳转到登录页面"):
            driver.find_element('xpath', '/html/body/div[6]/div/div[1]/div[2]/a[1]').click()
        with allure.step("在登录页面输入用户名"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[1]/input').send_keys(
                username)
        with allure.step("在登录页面输入密码"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[2]/div/input').send_keys(
                password)
        with allure.step("点击登录按钮"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[3]/button').click()
            time.sleep(3)
            # add_img_to_report(driver, "登录成功")
        with allure.step("获取登录成功后左上角的登录名"):
            login_text = driver.find_element('xpath', '/html/body/div[2]/div/ul[1]/div/div/em[2]').text
            time.sleep(1)
            allure.attach('<head></head><body> 内嵌一个HTML文档 </body>', '添加一个HTML内容的附件', allure.attachment_type.HTML)
        with allure.step("断言获取的登录名与预期结果是否一致"):
            assert result in login_text
        time.sleep(1)

执行测试,查看生成的报告

插入本地附件文件

向allure报告中插入本地文件的语法如下:

allure.attach.file(source, name=None, attachment_type=None, extension=None)

  • source:需要显示的内容,即本地文件的路径;
  • name:附件名称;
  • attachment_type:附件类型,可以是CSV、JPG、HTML等,由allure.attachment_type指定;
  • extension:附件的扩展名,可省略;

修改test_login.py增加插入本地文件附件

import time
import pytest
import allure
import xlrd

def readExecl():
    datas = list()
    book = xlrd.open_workbook('./testdata/login_data.xlsx')
    sheet = book.sheet_by_index(0)
    for item in range(1, sheet.nrows):
        datas.append(sheet.row_values(item))
    return datas


@pytest.mark.parametrize('username, password, result', readExecl())
class Test_login:
    @allure.description("测试登录功能的测试用例")
    @allure.epic("登录注册epic")
    @allure.feature("登录feature")
    @allure.story("登录story")
    @allure.title("登录成功title")
    @allure.tag("登录注册tag")
    @allure.link('http://101.43.8.10/', name='ShopXO商城')
    @allure.issue('https://demo16.zentao.net/bug-view-162.html', name='bug162')
    @allure.testcase('https://demo16.zentao.net/testcase-view-690-1.html', name='登录注册测试用例')
    @allure.severity(allure.severity_level.CRITICAL)
    def test_login_success(self, driver, username, password, result):
        with allure.step("点击登录按钮,跳转到登录页面"):
            driver.find_element('xpath', '/html/body/div[6]/div/div[1]/div[2]/a[1]').click()
        with allure.step("在登录页面输入用户名"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[1]/input').send_keys(
                username)
        with allure.step("在登录页面输入密码"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[2]/div/input').send_keys(
                password)
        with allure.step("点击登录按钮"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[3]/button').click()
            time.sleep(3)
        with allure.step("获取登录成功后左上角的登录名"):
            login_text = driver.find_element('xpath', '/html/body/div[2]/div/ul[1]/div/div/em[2]').text
            time.sleep(1)
            allure.attach.file(source='/Users/laobai/Desktop/testdata.csv', name='测试数据文件',
                               attachment_type=allure.attachment_type.CSV)
        with allure.step("断言获取的登录名与预期结果是否一致"):
            assert result in login_text
        time.sleep(1)

执行测试,查看生成的报告

插入运行过程中的截图

在项目的common目录下的tools.py中新建一个插入截图方法

import time
import allure


def add_img_to_report(driver, step_name, need_sleep=True):
    """
    截图并插入allure报告
    :param driver:
    :param step_name:
    :param need_sleep:
    :return:
    """
    if need_sleep:
        time.sleep(2)
    allure.attach(driver.get_screenshot_as_png(), step_name + '.png', allure.attachment_type.PNG)

修改test_login.py增加插入截图方法,这里在登录成功后进行截图并插入到报告中

import time
import pytest
import allure
import xlrd
from common.tools import add_img_to_report

def readExecl():
    datas = list()
    book = xlrd.open_workbook('./testdata/login_data.xlsx')
    sheet = book.sheet_by_index(0)
    for item in range(1, sheet.nrows):
        datas.append(sheet.row_values(item))
    return datas


@pytest.mark.parametrize('username, password, result', readExecl())
class Test_login:
    @allure.description("测试登录功能的测试用例")
    @allure.epic("登录注册epic")
    @allure.feature("登录feature")
    @allure.story("登录story")
    @allure.title("登录成功title")
    @allure.tag("登录注册tag")
    @allure.link('http://101.43.8.10/', name='ShopXO商城')
    @allure.issue('https://demo16.zentao.net/bug-view-162.html', name='bug162')
    @allure.testcase('https://demo16.zentao.net/testcase-view-690-1.html', name='登录注册测试用例')
    @allure.severity(allure.severity_level.CRITICAL)
    def test_login_success(self, driver, username, password, result):
        with allure.step("点击登录按钮,跳转到登录页面"):
            driver.find_element('xpath', '/html/body/div[6]/div/div[1]/div[2]/a[1]').click()
        with allure.step("在登录页面输入用户名"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[1]/input').send_keys(
                username)
        with allure.step("在登录页面输入密码"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[2]/div/input').send_keys(
                password)
        with allure.step("点击登录按钮"):
            driver.find_element('xpath',
                                '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[3]/button').click()
            time.sleep(3)
            add_img_to_report(driver, "登录成功")
        with allure.step("获取登录成功后左上角的登录名"):
            login_text = driver.find_element('xpath', '/html/body/div[2]/div/ul[1]/div/div/em[2]').text
        time.sleep(1)
        with allure.step("断言获取的登录名与预期结果是否一致"):
            assert result in login_text
        time.sleep(1)

执行测试,查看生成的报告

动态获取allrue.title()

前面介绍的方法中,指定title的allrue.title()是在代码中写死的,下面介绍通过使用pytest.mark.parametrize()从测试用例(测试数据文件)中动态获取。测试用例管理工具,比如禅道中,书写用例时,用例标题是必填字段,所以没有增加任何工作量。

  1. 修改login_data.xlsx增加title列

  2. 修改test_login.py,在pytest.mark.parametrize()增加参数title

    import time
    import pytest
    import allure
    import xlrd
    
    def readExecl():
        datas = list()
        book = xlrd.open_workbook('./testdata/login_data.xlsx')
        sheet = book.sheet_by_index(0)
        for item in range(1, sheet.nrows):
            datas.append(sheet.row_values(item))
        return datas
    
    @pytest.mark.parametrize('username, password, result, title', readExecl())
    class Test_login:
        @allure.description("测试登录功能的测试用例")
        @allure.epic("登录注册epic")
        @allure.feature("登录feature")
        @allure.story("登录story")
        @allure.title("登录用例-{title}")
        @allure.tag("登录注册tag")
        @allure.link('http://101.43.8.10/', name='ShopXO商城')
        @allure.issue('https://demo16.zentao.net/bug-view-162.html', name='bug162')
        @allure.testcase('https://demo16.zentao.net/testcase-view-690-1.html', name='登录注册测试用例')
        @allure.severity(allure.severity_level.CRITICAL)
        def test_login_success(self, driver, username, password, result, title):
            with allure.step("点击登录按钮,跳转到登录页面"):
                driver.find_element('xpath', '/html/body/div[6]/div/div[1]/div[2]/a[1]').click()
            with allure.step("在登录页面输入用户名"):
                driver.find_element('xpath',
                                    '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[1]/input').send_keys(
                    username)
            with allure.step("在登录页面输入密码"):
                driver.find_element('xpath',
                                    '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[2]/div/input').send_keys(
                    password)
            with allure.step("点击登录按钮"):
                driver.find_element('xpath',
                                    '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[3]/button').click()
                time.sleep(3)
            with allure.step("获取登录成功后左上角的登录名"):
                login_text = driver.find_element('xpath', '/html/body/div[2]/div/ul[1]/div/div/em[2]').text
                time.sleep(1)
            with allure.step("断言获取的登录名与预期结果是否一致"):
                assert result in login_text
            time.sleep(1)
  3. 执行测试,查看生成的报告

以上即完成了动态从测试用例中获取title。同样,epic、feature、stroy等字段也可以尝试从测试用例中进行动态获取。

增加ENVIRONMENT环境

新建environment.properties文件或environment.xml,二者均可

environment.properties

SystemVersion=macOS Mojave
PythonVersion=3.9.12
AllureVersion=2.20.1
ProjectName=PLScript Demo01
Author=laobai

environment.xml

<environment>
    <parameter>
        <key>SystemVersion</key>
        <value>macOS Mojave</value>
    </parameter>
    <parameter>
        <key>PythonVersion</key>
        <value>3.9.12</value>
    </parameter>
    <parameter>
        <key>AllureVersion</key>
        <value>2.20.1</value>
    </parameter>
    <parameter>
        <key>ProjectName</key>
        <value>PLScript Demo01</value>
    </parameter>
    <parameter>
        <key>Author</key>
        <value>laobai</value>
    </parameter>
</environment>

将上面两个文件之一,拷贝到执行测试生成过程文件(json\text)的目录中后,再执行生成报告的命令。(因为在执行生成过程文件时,经常会增加--clean-aaluredir参数,会删除整个目录中的内容)

生成的报告如下图所示:

Allure执行命令封装

从上面的介绍可知,生成最终的报告需要执行3次命令:

  1. 生成过程文件(json、text)
  2. 拷贝environment.xml文件到过程文件目录
  3. 生成最终的allure测试报告

可以将上面的3个步骤分别封装成一个方法,然后写一个run.py依次调用这3个方法即可。

代码略

执行测试:

$ python run.py
================================================================ test session starts ================================================================
platform darwin -- Python 3.9.12, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo02/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '6.2.5', 'pluggy': '1.0.0'}, 'Plugins': {'variables': '2.0.0', 'html': '3.2.0', 'base-url': '2.0.0', 'allure-pytest': '2.12.0', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home', 'Base URL': ''}
rootdir: /Users/laobai/workspaces/testdemo02
plugins: variables-2.0.0, html-3.2.0, base-url-2.0.0, allure-pytest-2.12.0, metadata-2.0.4
collected 2 items                                                                                                                                   

testcases/test_login.py::Test_login::test_login_success[PLChrome-laobai-a123456-laobai-登录成功-管理员账号登录] PASSED
testcases/test_login.py::Test_login::test_login_success[PLChrome-plscript-y123456-plscript-登录成功-商户账号登录] PASSED

================================================================ 2 passed in 20.03s =================================================================

Report successfully generated to /Users/laobai/workspaces/testdemo02/report/html

文章作者: 老百
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 老百 !
 上一篇
Python单元测试框架——pytest(五) Python单元测试框架——pytest(五)
钩子函数在pytest称之为Hook函数,是pytest框架的开发者,为了让用户更好的去扩展开发预留的一些函数。而预留的这些函数,在整个测试执行的生命周期中特定的阶段会自动去调用执行。
2022-12-24
下一篇 
Python单元测试框架——pytest(三) Python单元测试框架——pytest(三)
在自动化的测试中,经常需要把测试的数据分离出来,这样也是基于数据驱动的设计模式来进行思考。本节将会介绍4种文件格式如何分离、存储、读取测试数据。
2022-12-12
  目录