背景:接口測試用例運(yùn)行在Jenkins節(jié)點(diǎn)上蝴韭,在某些情況下够颠,比如網(wǎng)絡(luò)波動(dòng)等原因,會(huì)導(dǎo)致用例運(yùn)行失敗榄鉴,此時(shí)會(huì)觸發(fā)郵件和釘釘預(yù)警履磨,通知給到責(zé)任人,按照現(xiàn)有策略庆尘,當(dāng)本次構(gòu)建失敗時(shí)剃诅,會(huì)立馬觸發(fā)第二次構(gòu)建活動(dòng),若第二次構(gòu)建仍然失敗驶忌,則會(huì)再次觸發(fā)預(yù)警信息矛辕。在這種策略下,會(huì)導(dǎo)致相關(guān)責(zé)任人收到一些額外的無意義預(yù)警信息(如第一次構(gòu)建超時(shí)付魔,而第二次構(gòu)建成功)聊品,所以就多寫了一個(gè)腳本,在Jenkins中作為Robotframework用例的運(yùn)行入口几苍,當(dāng)有用例執(zhí)行失敗時(shí)翻屈,在所有cases執(zhí)行完成后,會(huì)選擇本次運(yùn)行失敗的cases再重試一次擦剑,然后合并兩次的測試報(bào)告文件妖胀。
腳本內(nèi)容很簡單芥颈,可拓展性很強(qiáng):
#!/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:
"""解析命令行傳入的參數(shù)"""
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")) # 包含用例標(biāo)簽
exclude_tag = _parse(("-e", "--excludeTag")) # 排除用例標(biāo)簽
env = _parse(("-E", "--env"), 'm') # 用例運(yùn)行環(huán)境
fm = _parse(("-F", "--format"), 'robot') # 用例文件后綴名
return include_tag, exclude_tag, env, fm, target
def first_run(target, env, include_tag, exclude_tag, fm):
"""首次運(yùn)行用例
項(xiàng)目的基本目錄結(jié)構(gòu)是固定的, 在命令行中寫死了變量文件的相對路徑.
"""
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:
"""解析用例運(yùn)行結(jié)果"""
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("當(dāng)前運(yùn)行目錄為: ", os.getcwd())
print("總測試條數(shù):{0}, 初次運(yùn)行時(shí),通過的用例數(shù): {1}, 失敗的用例數(shù): {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
如果要重新運(yùn)行整個(gè)套件赚抡,需要使用`rerunfailedsuites`, 如果只想重新運(yùn)行失敗的測試用例而不是套件中已通過的測試爬坑,則使用`rerunfailed`(必須保證case是獨(dú)立的)
-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'重復(fù)運(yùn)行失敗的用例: {cmd}')
os.system(cmd)
"""再次運(yùn)行失敗的用例"""
def merge_output():
"""合并xml文件,并生成測試報(bào)告
注意集成到j(luò)enkins中時(shí)涂臣,需要指定 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)
# 不論是否存在失敗的用例, 都會(huì)合并測試報(bào)告
merge_output()
if __name__ == '__main__':
main()
除-E
參數(shù)外盾计,其他都是robot
提供的的命令行參數(shù),在項(xiàng)目中使用了變量文件赁遗,來使得用例支持切換運(yùn)行環(huán)境署辉,-E
參數(shù)需要傳入用例運(yùn)行的環(huán)境,-i
或-e
參數(shù)用來傳入標(biāo)簽岩四,過濾本次要運(yùn)行的測試用例哭尝,可以傳入多個(gè)標(biāo)簽,如:H5ANDP1
剖煌、H5ORMini
材鹦、NotPaid
等。
在Jenkins項(xiàng)目配置中耕姊,構(gòu)建操作配置的 Execute Windows Batch Cmd 如下:
cd %WORKSPACE%/ParkTest/interface
python runrobot.py --env=%Env% -F robot -i %Tag% ./
exit 0
Env
和Tag
都是在參數(shù)化構(gòu)建時(shí)傳入的桶唐,并且設(shè)有默認(rèn)值。
這樣就可以減少一些無效的報(bào)錯(cuò)郵件了鬼店。
關(guān)于以上方案,有一點(diǎn)還要進(jìn)行特別說明黔龟,那就是項(xiàng)目中測試用例之前必須是相互獨(dú)立的妇智。保持Case獨(dú)立性我認(rèn)為是很有必要的,每一個(gè) Test Case 應(yīng)該只測試一種場景氏身,根據(jù)case復(fù)雜程度巍棱,不同場景同樣可大可小,但不能相互影響蛋欣。當(dāng)我們有隨機(jī)的跑其中某個(gè)Case或亂序的跑這些Cases時(shí)航徙,測試的結(jié)果都應(yīng)該是準(zhǔn)確的。Suite level和Directory level同樣要注意獨(dú)立性的問題陷虎。保持Case的獨(dú)立性到踏,這一點(diǎn)應(yīng)當(dāng)作為自動(dòng)化用例編寫規(guī)范杠袱,嚴(yán)格要求組內(nèi)其他成員。
【To be continud...】