Jenkins+RobotFramework 失败用例重执行方案

背景:接口测试用例运行在Jenkins节点上,在某些情况下,比如网络波动等原因,会导致用例运行失败,此时会触发邮件和钉钉预警,通知给到责任人,按照现有策略,当本次构建失败时,会立马触发第二次构建活动,若第二次构建仍然失败,则会再次触发预警信息。在这种策略下,会导致相关责任人收到一些额外的无意义预警信息(如第一次构建超时,而第二次构建成功),所以就多写了一个脚本,在Jenkins中作为Robotframework用例的运行入口,当有用例执行失败时,在所有cases执行完成后,会选择本次运行失败的cases再重试一次,然后合并两次的测试报告文件。

脚本内容很简单,可拓展性很强:

#!/usr/bin/env python
# -*- coding:utf8 -*-

import getopt
import os
import sys
from pathlib import Path
from robot.api import ExecutionResult

def parse_args() -> tuple:
    """解析命令行传入的参数"""
    opts, args = getopt.getopt(sys.argv[1:], '-i:-e:-F:-E:', ["includeTag=", "excludeTag=", "format=", "env="])

    try:
        target = args[0]
    except IndexError:
        target = "./"

    def _parse(option, default_value=None):
        if isinstance(option, tuple):
            temp = [opt_value for (opt_name, opt_value) in opts if opt_name in option]
        else:
            temp = [opt_value for (opt_name, opt_value) in opts if opt_name == option]

        return temp[0] if len(temp) > 0 else default_value

    include_tag = _parse(("-i", "--includeTag"))  # 包含用例标签
    exclude_tag = _parse(("-e", "--excludeTag"))  # 排除用例标签
    env = _parse(("-E", "--env"), 'm')  # 用例运行环境
    fm = _parse(("-F", "--format"), 'robot')  # 用例文件后缀名

    return include_tag, exclude_tag, env, fm, target


def first_run(target, env, include_tag, exclude_tag, fm):
    """首次运行用例
    项目的基本目录结构是固定的, 在命令行中写死了变量文件的相对路径.
    """
    if include_tag:
        cmd = f"robot -F {fm} -i {include_tag} --output output_origin.xml --log NONE --report NONE -V variables.py:{env} -V ../Common/Variables/commonVars.py:{env} {target}"
    elif exclude_tag is not None:
        cmd = f"robot -F {fm} -e {exclude_tag} --output output_origin.xml --log NONE --report NONE -V variables.py:{env} -V ../Common/Variables/commonVars.py:{env} {target}"
    else:
        cmd = f"robot -F {fm} --output output_origin.xml --log NONE --report NONE -V variables.py:{env} -V ../Common/Variables/commonVars.py:{env} {target}"

    print(f'First run cmd >>>> {cmd}')
    os.system(cmd)


def parse_robot_result(xml_path) -> bool:
    """解析用例运行结果"""
    suite = ExecutionResult(xml_path).suite

    fail = {}
    for test in suite.tests:
        if test.status == "FAIL":
            fail.update({test.name: test.status})

    all_tests = suite.statistics.critical
    print("*" * 50)
    print("当前运行目录为: ", os.getcwd())
    print("总测试条数:{0}, 初次运行时,通过的用例数: {1}, 失败的用例数: {2}".format(all_tests.total, all_tests.passed, all_tests.failed))
    if all_tests.failed > 0:
        print("其中失败的用例信息为: %s" % str(fail))
    print("*" * 50)

    return all_tests.failed > 0


def rerun_fail_case(target, env, include_tag, exclude_tag, fm):
    """ # TODO
    如果要重新运行整个套件,需要使用`rerunfailedsuites`, 如果只想重新运行失败的测试用例而不是套件中已通过的测试,则使用`rerunfailed`(必须保证case是独立的)
    -R, --rerunfailed <file>
        Selects failed tests from an earlier output file to be re-executed.
    -S, --rerunfailedsuites <file>
        Selects failed test suites from an earlier output file to be re-executed.
    """

    if include_tag:
        cmd = f"robot -F {fm} -i {include_tag} -V variables.py:{env} -V ../Common/Variables/commonVars.py:{env} --rerunfailed output_origin.xml --output output_rerun.xml {target}"
    elif exclude_tag is not None:
        cmd = f"robot -F {fm} -e {exclude_tag} -V variables.py:{env} -V ../Common/Variables/commonVars.py:{env} --rerunfailed output_origin.xml --output output_rerun.xml {target}"
    else:
        cmd = f"robot -F {fm} -V variables.py:{env} -V ../Common/Variables/commonVars.py:{env} --rerunfailed output_origin.xml --output output_rerun.xml {target}"

    print(f'重复运行失败的用例: {cmd}')
    os.system(cmd)
    """再次运行失败的用例"""


def merge_output():
    """合并xml文件,并生成测试报告
    注意集成到jenkins中时,需要指定 Output xml name为merge.xml
    """
    os.system("rebot --merge --output merge.xml *.xml")


def main():
    include_tag, exclude_tag, env, fm, target = parse_args()

    # 切换到output目录
    if Path(target).is_dir():
        os.chdir(Path(target))
    else:
        os.chdir(Path(target).parent)

    for xml in Path.cwd().glob("*.xml"):
        os.remove(xml)

    first_run(target, env, include_tag, exclude_tag, fm)

    failed = parse_robot_result("output_origin.xml")
    if failed:
        rerun_fail_case(target, env, include_tag, exclude_tag, fm)

    # 不论是否存在失败的用例, 都会合并测试报告
    merge_output()


if __name__ == '__main__':
    main()

-E参数外,其他都是robot提供的的命令行参数,在项目中使用了变量文件,来使得用例支持切换运行环境,-E参数需要传入用例运行的环境,-i-e参数用来传入标签,过滤本次要运行的测试用例,可以传入多个标签,如:H5ANDP1H5ORMiniNotPaid等。

在Jenkins项目配置中,构建操作配置的 Execute Windows Batch Cmd 如下:

cd %WORKSPACE%/ParkTest/interface
python runrobot.py --env=%Env% -F robot -i %Tag% ./
exit 0

EnvTag都是在参数化构建时传入的,并且设有默认值。

在构建后操作中,使用Robot Framework插件收集构建结果,由于上面在脚本中修改了默认的输出文件名,这里要对应进行配置,如下

当项目第一次构建失败时,第二次构建只会运行之前失败的测试用例,并合并两次生成的测试报告,在测试报告中展示如下:
log.html
我配置的邮件通知模板

这样就可以减少一些无效的报错邮件了。

关于以上方案,有一点还要进行特别说明,那就是项目中测试用例之前必须是相互独立的。保持Case独立性我认为是很有必要的,每一个 Test Case 应该只测试一种场景,根据case复杂程度,不同场景同样可大可小,但不能相互影响。当我们有随机的跑其中某个Case或乱序的跑这些Cases时,测试的结果都应该是准确的。Suite level和Directory level同样要注意独立性的问题。保持Case的独立性,这一点应当作为自动化用例编写规范,严格要求组内其他成员。

【To be continud...】

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容