Skip to content

24对外输出,让测试报告自己说话

你好,我是蔡超,欢迎来到第 24 课时。通过前面章节的学习,我们已经掌握了测试框架的方方面面,今天我们来讲解测试框架的另外一个重要组成部分 ------ 测试报告。

测试报告在测试框架中的重要性不言而喻,有了测试报告,我们不仅可以在单次测试运行后,根据测试结果直观地对软件质量有个大致的评估,还可以汇总历次测试运行的情况,得出如下信息。

  • 随着版本的历次变更,质量是变好了还是变坏了?

  • 同一个模块下的测试用例, 在不同版本下执行,需要修改测试用例的频率是多少?

通过此指标可以看出这个模块更新,或者被其他模块影响的频率高低。

  • 哪条测试用例永远成功?考虑是不是测试检查点写得不足,如果不是,考虑是否可以删除掉这条用例。

  • 哪条测试用例永远失败,或者大概率失败?

首先检查测试脚本是否存在问题。如果没有问题,检查这个测试用例对应哪个功能点?失败的原因是什么?

既然测试报告能告诉我们这么多信息,那么测试报告应该如何开发呢?测试报告又应该包括哪些内容呢?这一讲的大致内容如下图所示,可供你参考。

测试报告应该包括哪些内容?

在我看来,测试报告至少需要包括项目总览和执行情况分析这两方面的信息。

1. 项目总览

  • 本次测试执行了多长时间?

  • 总共执行了多少测试用例?

  • 有多少测试用例执行成功?

  • 有多少测试用例执行失败?

  • 有多少测试用例是非正常失败的?

不是由于断言失败,而是由于环境不稳定,运行中突发错误而导致的失败。

  • 每条测试用例执行了多长时间?

  • 本次测试在哪个环境运行?是开发环境、集成测试环境还是生产环境?

  • 本次测试运行在哪个操作系统上?

2. 执行情况分析

针对每一次测试运行,测试报告应该提供最基本的测试分析,包括:

  • 按照测试失败划分的测试分析图

  • 按模块划分的测试分析图

  • 按照测试用例重要程度、优先级划分的测试分析图

  • 按照测试执行时间划分的测试分析图

这些不同维度的分析图,可用从各个层面来反映软件的质量情况。

所以,依托测试报告进行分析和总结,我们能够有理有据,以数字形式直观反映项目暴露出的质量问题,从而催促各个相关方后续改进。

在当前市面上所有第三方或者自研的测试报告系统中,Allure 是最全面,且支持的测试框架最多的一个测试报告系统。它是开源的测试报告框架,它旨在创建让团队每一个人都清楚明了的测试报告。下面我将以 Allure 报告为例,详细讲解测试报告的开发使用。

Allure 报告生成的原理

Allure 报告是基于标准的 xUnit 结果输出,再添加补充数据而生成的,其报告的生成基于如下两个步骤。

  • 在测试执行期间,一个名为 Adapter 的小型 library 被连接到测试框架中,并将所有测试执行的信息保存到 XML 文件中。对于大多数编程语言下的流行测试框架(例如 python 语言中的 pytest,Java 中的 jUnit 等),Allure 都默认为其提供了 Adapter。

  • 获取 XML 文件后,Allure 会将这些 XML 文件转换为 HTML 报告。这一步骤可以通过持续集成系统的 Allure 插件,或者命令行命令实现。

Allure 报告特点

Allure 报告之所以受到开发、测试,甚至管理人员的推崇,是因为它有如下明显的特点。

  • 从开发/质量保证的角度看,Allure 报告可以缩短常见缺陷的生命周期

可以将测试失败划分为 bug 和损坏的(Broken)测试,还可以配置日志、步骤、固定装置、附件、时间、历史记录,以及与 TMS 的集成和 Bug 跟踪系统,方便将 Task 与负责 Task 开发人员和测试人员绑定,从而使开发和测试人员第一时间掌握所有信息。

  • 从管理者的角度看,Allure 提供了一个清晰的"全局"视野。

包括本次测试涵盖了哪些功能,Bug 在哪个 case 用例中被发现,以及整体测试用例、单条测试用例的执行时间等信息。

Allure 测试报告一览

Allure 测试报告除了涵盖测试运行的全面信息外,还提供各种维度的分析图,包括如下几个部分。

  • 项目总览(OverView)

项目总览显示了总体测试运行的一系列统计信息。

  • 按缺陷种类分析(Categories)

缺陷种类分析显示了所有不同原因引起的失败,并分类展示。

  • 按测试套件分析(Suites)

测试套件分析显示了按照套件和类划分的所有的测试执行情况。

  • 图表模块(Graphs)

图表模块,包括按照不同维度分析的各种图表(例如测试状态表分析,测试用例等级分析表,测试执行时间分析表等等)。

  • 按执行时间分析(Timeline)

按执行时间分析模块,详细列出了各个测试用例的执行时间,你可以筛选出那些运行时间最长的测试用例进行优化。

  • 针对 BDD 驱动的测试用例进行分析(Behaviors)

这里主要是根据 Epic、Feature 和 Story 标签对测试结果进行分组。

  • 按照 Package 进行分析(Packages)

Package 模块列出了按照 Package 维度进行分析的详细图表。

了解了 Allure 测试报告的模块划分,我们来看下,如何配置使得这些模块反映测试的执行情况。

Allure 测试报告开发配置指南

Allure 有很多独有的功能,可用来方便自定义测试报告,下面我将详细为你讲解。

1.Allure 的安装和配置

在不同操作系统下安装 Allure 报告的方式各有不同。关于如何安装、配置 Allure 测试报告,我在前面的章节《10 | 你的第一个 API 测试框架(二)》介绍过,你可以回顾一下。

你也可以直接通过如下方式安装:

html
pip install allure-pytest

注意,如果你安装过 Allure 2.0 之前的版本,你需要先将之前的版本卸载。

2.Allure 测试报告基础使用

Allure 测试报告安装配置好后,由 pytest 运行的测试便可以直接使用。假设当前你有个项目名为 allureDemo,则你可以采用如下方式使用 Allure 报告:

  • 首先运行你的测试
python
# 直接执行allureDemo项目下的所有测试用例,并将测试报告文件夹allure_reports放在项目根目录下
D:\_Automation\allureDemo>pytest -s  -v --alluredir=./allure_results
  • 运行完成后,打开 allure 报告
python
# 以Win10为例:
# 1. 使用快捷键 Win + R 调起运行提示框
# 2. 输入"cmd"进入到命令行
# 3. 切换目录到项目根目录,本例中在D:\_Automation\allureDemo>:
# 4. 输入命令生成allure报告:
allure serve allure_reports

此时,Allure 会帮你生成测试报告会自动打开。

Allure 报告实战

为了清晰地讲解 Allure 各个模块的使用,但又尽量少地引入其他代码,我将重新建立一个项目来详解 Allure 测试报告。

html
|--allureDemo
    |--tests
        |--test_baidu.py
        |--test_basic_report.py
        |--__init__.py
    |--conftest.py

其中 conftest.py 里的代码如下:

python
import allure
import pytest

def pytest_addoption(parser):
    parser.addoption(
        "--flag", action="store_true", default=False, help="set skip or not")
    parser.addoption(
        "--browser", action="store", default="Firefox", help="set browser")

@pytest.fixture(scope='session')
def get_flag(request):
    return request.config.getoption('--flag')

@pytest.fixture(scope='session')
def get_browser(request):
    return request.config.getoption('--browser')

@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """
      本hook用于制作测试报告
      :param item:测试用例对象
      :param call:测试用例的测试步骤
               执行完常规钩子函数返回的report报告有个属性叫report.when
                when='setup' 代表返回setup 的执行结果
                when='call' 代表返回call 的执行结果
      :return:
    """
    outcome = yield
    rep = outcome.get_result()
    if (rep.when == "call" or rep.when == 'setup') and (rep.failed or rep.skipped):
        try:
            if "initial_browser" in item.fixturenames:
                web_driver = item.funcargs['initial_browser']
            else:
                # 如果找不到driver,则直接return
                return
            allure.attach(web_driver.get_screenshot_as_png(), name="wrong picture",
                          attachment_type=allure.attachment_type.PNG)
        except Exception as e:
            print("failed to take screenshot".format(e))

在这个代码中,我分别定义了两个命令行参数 flag 和 browser。

  • flag:只有两个值 True 和 False,当用户不传 flag 参数时,默认为 False,当用户传递 flag 时,值为 True。

  • browser:代表要启用的浏览器,默认是 Firefox 浏览器。

对应地,get_flag 和 get_browser 这两个 fixture 就是分别用来取 flag 和 browser 的值。

被装饰器 @pytest.hookimpl(tryfirst=True, hookwrapper=True) 装饰的函数 pytest_runtest_makereport,是 pytest 提供的 Hook 函数,它有以下两个作用:

  • 可以获取到测试用例不同执行阶段的结果(setup,call,teardown);

  • 可以获取钩子方法的调用结果(yield 返回一个 result 对象)和调用结果的测试报告(返回一个 report 对象,即 _pytest.runner.TestReport)。

而在本例中,我通过它实现了当测试失败或者被 skip 时,自动根据 WebDriver 提供的 get_screenshot_as_png() 方法来截图。

接着,我们来看下文件 test_baidu.py 的内容:

python
# -*- coding: utf-8 -*-
import time
import allure
import pytest
from selenium import webdriver

@allure.epic('baidu')
@allure.description('测试百度的搜索功能')
@allure.severity('BLOCKER')
@allure.feature("百度搜索")
@allure.testcase("http://www.baidu.com")
@pytest.mark.baidu
class TestBaidu:
    @pytest.fixture
    def initial_browser(self, get_browser):
        if get_browser:
            if get_browser.lower() == "Chrome":
                self.driver = webdriver.Chrome()
            elif get_browser.lower() == "firefox":
                self.driver = webdriver.Firefox()
            else:
                self.driver = webdriver.Chrome()
        else:
            self.driver = webdriver.Chrome()
            self.driver.implicitly_wait(30)
        self.base_url = "http://www.baidu.com/"
        yield self.driver
        self.driver.quit()
    @allure.title("测试百度搜索正确")
    @pytest.mark.parametrize('search_string, expect_string', [('iTesting', 'iTesting'), ('helloqa.com', 'iTesting')])
    def test_baidu_search(self, initial_browser, search_string, expect_string):
        driver = initial_browser
        driver.get(self.base_url + "/")
        driver.find_element_by_id("kw").send_keys(search_string)
        driver.find_element_by_id("su").click()
        time.sleep(2)
        search_results = driver.find_element_by_xpath('//*[@id="1"]/h3/a').get_attribute('innerHTML')
        print(search_results)
        assert (expect_string in search_results) is True
    @allure.title("测试百度搜索失败")
    @pytest.mark.parametrize('search_string, expect_string', [('iTesting', 'isGood')])
    def test_baidu_search_fail(self, initial_browser, search_string, expect_string):
        driver = initial_browser
        driver.get(self.base_url + "/")
        driver.find_element_by_id("kw").send_keys(search_string)
        driver.find_element_by_id("su").click()
        time.sleep(2)
        search_results = driver.find_element_by_xpath('//*[@id="1"]/h3/a').get_attribute('innerHTML')
        assert (expect_string in search_results) is True

if __name__ == "__main__":
    pytest.main(["-m", "baidu", "-s", "-v", "-k", "test_baidu_search", "test_baidu_fixture_sample.py"])

在这个文件中,我创建了一个测试类 TestBaidu 和两个测试方法 test_baidu_search() 和 test_baidu_search_fail()。注意:这两个方法有很多关于 allure 的装饰器,例如 @allure.epic、 @allure.feature 等,它们分别代表什么含义我等下再讲,我们继续往下看。

最后,test_basic_report.py 的内容如下:

python
import allure
import pytest
from flaky import flaky

@allure.epic("演示下allure支持的测试")
@allure.description('测试模块1用来对模块1进行测试')
@allure.feature("测试模块1")
@allure.story("测试模块1_story1")
@allure.testcase("http://www.baidu.com")
@pytest.mark.basic
class TestBasic:
    @allure.step("测试步骤1 -- 判断登录成功")
    @allure.severity('BLOCKER')
    def test_login(self):
        """模拟成功的测试用例"""
        assert 1 == 1
    @flaky
    @allure.step("测试步骤2 -- 查询余额")
    @allure.severity("normal")
    def test_savings(self):
        """模拟失败的测试用例"""
        assert 1 == 0
    @allure.step("测试步骤2.1 -- 查询余额")
    @allure.severity("normal")
    def test_savings1(self):
        """模拟失败的测试用例"""
        assert 1 == 0
    @allure.description("调试用,不执行")
    def test_deposit_temp(self):
        """模拟skip的测试用例"""
        pytest.skip('调试用例,skip')
    @allure.issue("http://itesting.club", "此处之前有bug,bug号如上")
    @allure.step("测试步骤3 -- 取现")
    def test_deposit(self):
        raise Exception('oops')
    @pytest.mark.xfail(ccondition=lambda: True, reason='this test is expecting failure')
    def test_xfail_expected_failure(self):
        """被期望的失败"""
        assert False
    @pytest.mark.xfail(condition=lambda: True, reason='this test is expecting failure')
    def test_xfail_unexpected_pass(self):
        """期望失败,但是却成功,会被标记为不期望的成功"""
        assert True
    @allure.step("测试步骤4 -- teardown")
    def test_skip_by_triggered_condition(self, get_flag):
        if get_flag == True:
            pytest.skip("flag 是true时, skip掉此条用例")

在这个文件中,为了模拟到所有的测试运行情况,我人为定义了一些 pass 和 fail,以及 skip、xfail 的情况。

1.Allure 测试报告装饰器

在运行整个测试之前,我们来学习下 Allure 测试报告的各个装饰器及其作用:

关于每一个装饰器的具体用法,可以直接参考上文中的代码示例。

2.为 Allure 测试报告添加 Environment

现在,我来生成下测试报告:

python
# 在项目根目录下执行
# 本例中,根目录是D:\_Automation\allureDemo>
D:\_Automation\allureDemo>pytest -m baidu -s  -v --alluredir=./allure_results

执行成功后,通过如下命令打开测试报告:

html
allure serve ./allure_results

可以看到如下的结果。默认情况下,Allure 生成的报告是不带 Environment 信息的,如下图中"1.Environment 默认没有"所示。

那么如何让测试报告带上 Environment 信息呢?

  • 首先,执行完测试用例后,创建文件 environment.properties,其内容格式如下:
html
Browser=Chrome
Browser.Version=86.0.4240
Environment=QA

注意:这里为 key=value 的格式。这个文件你可以通过编写相关函数动态获取每次执行时的真实值,然后写入 environment.properties 文件。这里为了方便,我直接 Hard Coded.

  • 然后,把文件 environment.properties 拷贝到你在执行测试用例时设置的 allure 报告目录下,在本例中为 allure_results 这个目录。

  • 最后,执行如下命令:

html
allure serve ./allure_results

你会发现 Environment 里出现了我们刚刚配置的值:

3.为 Allure 测试报告增加错误类型

在默认情况下,Allure 仅仅会列出以下两种类型的 Categories。

  • Product Defects(failed tests)

表示真正的测试执行失败,如果 Categories 里出现这个错误,通常表明测试用例最后的输出跟期望不符合,有 Bug 出现。

  • Test Defects(broken tests)

表示测试用例本身有问题导致的错误,如果 Categories 里出现这个错误,通常表明测试用例在执行过程中出错了,需要我们进一步调查原因。

如果你仔细观察 test_basic_report.py 里的代码,你可以看到,我们很多用例是要 skip,或者需要根据用户的传参来 skip 的,这些测试用例没有被反映到 Categories 里。

那么如何自定义 Categories 呢?

  • 首先,创建名称为 categories.json 的文件,内容如下:
json
[
  {
    "name": "Ignored tests",
    "matchedStatuses": ["skipped"]
  },
  {
    "name": "Infrastructure problems",
    "matchedStatuses": ["broken", "failed"],
    "messageRegex": ".*bye-bye.*"
  },
  {
    "name": "Outdated tests",
    "matchedStatuses": ["broken"],
    "traceRegex": ".*FileNotFoundException.*"
  },
  {
    "name": "Product defects",
    "matchedStatuses": ["failed"]
  },
  {
    "name": "Test defects",
    "matchedStatuses": ["broken"]
  }
]
  • 然后,把文件 categories.json 拷贝到你在执行测试用例时设置的 allure 报告目录下,在本例中为 allure_results 这个目录。

  • 最后,执行如下命令:

html
allure serve ./allure_results

你会发现 Categories 里出现了我们刚刚配置的值 Ignored tests:

同样地,默认生成的 Allure 报告不包括历次运行信息 Trends,如果想添加历次运行信息到Trends,步骤如下。

  • 执行完测试后,不要执行 allure serve 命令,转而执行 allure generate。
python
allure generate ./allure_results

这个操作会生成一个新的文件夹,名为 allure-report。拷贝 allure-report 文件夹下的 history 文件夹,及其子文件夹到 allure_results 这个目录中。

  • 在新的一次测试执行后执行 allure serve,即可把历史记录带到 Allure 报告中。
html
allure serve ./allure_results

执行完后,打开 Allure 测试报告,你将看到 Trend 的内容。

5.为 Allure 测试报告添加执行人

同样地,默认的 Allure 测试报告也不显示 Executor,这是因为 Executor 通常是由 Builder 自动生成的,比如通过 Jenkins pluginAllure Jenkins Plugin 来生成。

关于如何使用 Allure Jenkins Plugin 配置 Allure,你可以在课后另外了解。

  • 当然你也可以自己生成, 首先创建名称为executor.json 的文件,内容如下:
json
{
  "name": "iTesting",
  "type": "jenkins",
  "url": "http://helloqa.com",
  "buildOrder": 3,
  "buildName": "allure-report_deploy#1",
  "buildUrl": "http://helloqa.com#1",
  "reportUrl": "http://helloqa.com#1/AllureReport",
  "reportName": "iTesting Allure Report"
}
  • 然后,拷贝 executor.jsonallure_results 这个目录中去。

  • 最后, 执行如下命令即可:

html
allure serve ./allure_results

执行完后,打开 Allure 测试报告,你将看到 Executor 的信息:

6.Allure 测试报告实现错误自动截图

在测试时,特别是 UI 自动化测试错误发生时,我们会想通过系统截图的方式,来更清楚地了解系统当时的状态,于是错误截图就很必要。

使用 Allure 自动实现错误截图,可以参考下我在本讲 conftest.py 这个文件中定义的如下函数:

html
pytest_runtest_makereport

现在,我们来看下错误截图的实际效果。

首先,通过如下命令运行所有测试:

python
pytest -s  -v --alluredir=./allure_results --flag --browser chrome

执行成功后,通过如下命令打开测试报告:

html
allure serve ./allure_results

可以看到如下结果:

点击图中的 "FEATURES BY STORIES", 进入 Behaviors 页面,展开后你可以看到下图:

可以看到,第 3 条测试用例执行失败了,截图信息也自动保存了。

7.通过 Allure 测试报告查看测试用例详情

Allures 生成后,我们可以通过点击左侧的导航栏,进入到不同的页面,观察 Allure 的不同维度的测试分析。这里我不再一一阐述,我重点分享一下如下页面:

你点击任何一条测试用例,都能在 OverView 里看到这个测试用例的详细情况,包括测试所属的模块、测试用例的重要程度,以及这个测试用例对应 Jira 的 story 等信息,非常清晰明了。

自研的测试报告

除了用 Allure 来生成测试报告外,你的测试报告也可以自研,那么自研怎么操作呢?

最简单的方式是你写一个 HTML 模板,然后把涉及运行信息的要素,比如运行环境、执行人、测试成功条数、测试失败条数定义为变量,然后在你的自研框架运行完后,把这些变量替换为真实的值即可。

至于图表的操作,你可以使用 hicharts、echarts 等图表可视化工具完成。因为这里涉及 HTML 还有一些前端的代码知识,我就不再详细介绍。

总结

通过本章的学习,你应该对如何在 pytest 中应用 Allure 测试报告非常熟悉了,对于自研的测试报告的实现原理你应该也能有一点了解。

测试报告在测试中的重要性不言而喻,一个好的测试报告,不仅可以让项目中的每个人时刻了解当前项目的质量情况,还可以通过对历史版本的测试报告进行分析,提炼出软件质量的演变过程。

除此之外,一个美观、功能齐全的测试报告,也能让项目管理者更加理解测试工程师的辛勤付出,更助于项目组获取更多的资源支持,可见测试报告在技术之外的重要性。

好的,我是蔡超,我们下节课再见。

关于更多开发测试报告的内容,可以关注我的公众号 iTesting 并回复"测试报告"进行查阅。


课程评价入口,挑选 5 名小伙伴赠送小礼品~