Jenkins教程-10-发送飞书测试报告通知
上一小节我们学习了发送企业微信测试报告通知的方法,本小节我们讲解一下发送飞书测试报告通知的方法。
1、自动化用例执行完后,使用pytest_terminal_summary钩子函数收集测试结果,存入本地status.txt文件中,供Jenkins调用
conftest.py代码如下:
#conftest.py def pytest_terminal_summary(terminalreporter, exitstatus, config):"""收集测试报告summary,并存入status.txt文件中,供Jenkins调用"""print("pytest_terminal_summary")passed_num = len([i for i in terminalreporter.stats.get('passed', []) if i.when != 'teardown'])failed_num = len([i for i in terminalreporter.stats.get('failed', []) if i.when != 'teardown'])error_num = len([i for i in terminalreporter.stats.get('error', []) if i.when != 'teardown'])skipped_num = len([i for i in terminalreporter.stats.get('skipped', []) if i.when != 'teardown'])total_num = passed_num + failed_num + error_num + skipped_numtest_result = '测试通过' if total_num == passed_num + skipped_num else '测试失败'duration = round((time.time() - terminalreporter._sessionstarttime), 2)# 定义目录路径directory_path = './reports/'# 确保文件所在的目录存在os.makedirs(os.path.dirname(directory_path), exist_ok=True)# 定义文件路径file_path = os.path.join(directory_path, 'status.txt')with open(file_path, 'w', encoding='utf-8') as f:f.write(f'TEST_TOTAL={total_num}\n')f.write(f'TEST_PASSED={passed_num}\n')f.write(f'TEST_FAILED={failed_num}\n')f.write(f'TEST_ERROR={error_num}\n')f.write(f'TEST_SKIPPED={skipped_num}\n')f.write(f'TEST_DURATION={duration}\n')f.write(f'TEST_RESULT={test_result}\n')
本地文件status.txt中收集测试结果示例:

2、Jenkins中安装Environment Injector 和description setter 插件
Environment Injector插件用于注入环境变量
自动化测试任务配置中,添加构建步骤

填写测试结果收集文件status.txt的路径

description setter用于构建后设置任务描述
将status.txt中的的测试结果字段映射到任务描述中

执行任务构建后,任务描述中会显示构建的测试结果,如下

3、安装python-jenkins 库,读取自动化测试任务构建后的测试结果描述信息
pip install python-jenkins
代码如下
# qywechat_remind.py
import json
from datetime import datetime
import jenkins
import requests
import jmespathhost = "http://localhost:8080/"
username = 'admin'
password = 'xxxxxxxxxx'
webhook = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=f4444444444-c1d3-47f2-be78-098f80c2d194"
env = "test"
stage = "回归测试"
job = "auto_api_test"
maintainer = "米兔1号"
server = jenkins.Jenkins(host, username=username, password=password)
last_build_number = server.get_job_info(job)['lastCompletedBuild']['number']
build_info = server.get_build_info(job, last_build_number)
console_url = build_info['url'] + "console"
report_url = build_info['url'] + 'allure'
# report_url = ip_host + report_url.split(":")[-1]
test_status = json.loads(build_info['description'])
print("构建测试结果描述信息:", test_status)total = test_status["total"]
passed = test_status["passed"]
passed_ratio = round(passed / total, 4) * 100
failed = test_status["failed"]
failed_ratio = round((100 - passed_ratio), 2)
error = test_status["error"]
skipped = test_status["skipped"]
duration = test_status["duration"]
build_time = datetime.fromtimestamp(build_info['timestamp'] / 1000).strftime('%Y-%m-%d %H:%M:%S')
success = total == (passed + skipped) if passed != 0 else False
执行上述代码,可以看出,已经获取到jenkins任务的测试结果信息了

4、上一步获取到的测试结果信息,包装成消息体,调用飞书机器人发送群消息接口,自动发送消息到群里
飞书机器人的配置和接口,请参考:开发文档 - 飞书开放平台
飞书发送测试结果消息的整体代码如下:
import json
from datetime import datetime
import jenkins
import requests
import jmespathhost = "http://localhost:8080/"
username = 'admin'
password = 'xxxxx'
webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/c82222qqw64de8a-cac3-4523-b234-85269cf4945d"
env = "test"
stage = "回归测试"
job = "auto_api_test"
maintainer = "米兔1号"
server = jenkins.Jenkins(host, username=username, password=password)
last_build_number = server.get_job_info(job)['lastCompletedBuild']['number']
build_info = server.get_build_info(job, last_build_number)
print("构建信息:", build_info)
console_url = build_info['url'] + "console"
print("console:", console_url)
report_url = build_info['url'] + 'allure'
print("report_url:", report_url)
test_status = json.loads(build_info['description'])
print("测试结果:", test_status)
total = test_status["total"]
passed = test_status["passed"]
passed_ratio = round(passed / total, 4) * 100
print("passed_ratio", passed_ratio)
failed = test_status["failed"]
failed_ratio = round((100 - passed_ratio), 2)
print("failed:", failed_ratio)
error = test_status["error"]
skipped = test_status["skipped"]
duration = test_status["duration"]
build_time = datetime.fromtimestamp(build_info['timestamp'] / 1000).strftime('%Y-%m-%d %H:%M:%S')
success = total == (passed + skipped) if passed != 0 else False
# 使用Jenkins API token 模拟登录
USERNAME = "admin"
# Jenkins API token
TOKEN = "113b81ae7fd66046859f1b9833d391621a"
url_suites = f"{report_url}/data/suites.json"
# print("url_suites", url_suites)
res = requests.get(url_suites, auth=(USERNAME, TOKEN))
# print("res", res.content)
s_url = f"{report_url}/#suites/"
# print('s_url', s_url)
url_raw_list = jmespath.search("children[].children[].children[].children[?status=='failed'||status=='broken'].{name:name,parentUid:parentUid,uid:uid,status:status,tags:tags}",res.json())
# print("url_raw_list", url_raw_list)url_list = []
for raw in url_raw_list[0]:url_dict = {"name": raw["name"], "url": s_url + raw["parentUid"] + "/" + raw["uid"] + "/", "uid": raw["uid"],"status": raw["status"], "author": raw["tags"][0]}url_list.append(url_dict)
# print("url_list", url_list)url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"payload = json.dumps({"app_id": "cli_asss6baddbc63b4500c","app_secret": "c39BsssskuRpZqbXzgcyab7fqgRVkTfT7skL"
})
headers = {'Content-Type': 'application/json'
}response = requests.request("POST", url, headers=headers, data=payload).json()
# red = "#FF0000"
# green = "#00ff00"
a = 'green'
b = 'red'
c = 'yellow'
card_demo = {"msg_type": "interactive","card": {"elements": [{"tag": "div","text": {"content": f"-**任务名称**:{job}\n\n-**测试阶段**:{stage}\n\n-**测试结果**:<font color={a if success else b}>{'通过~' if success else '失败!'}</font> {chr(0x1f600) if success else chr(0x1f627)}\n\n-**用例总数**:{total}\n\n-**通过数**:<font color={a}>{passed}</font>\n\n-**通过率**:{passed_ratio}%\n\n-**失败数**:<font color={b}>{failed}</font>\n\n-**失败率**:{failed_ratio}%\n\n-**错误数**:{error}\n\n-**跳过数**:{skipped}\n\n-**执行人**:@{maintainer}\n\n-**执行时间**:{build_time}\n\n-**执行耗时**:{duration}s\n\n","tag": "lark_md"}}, {"actions": [{"tag": "button","text": {"content": "查看测试报告","tag": "lark_md"},"url": report_url,"type": "primary","value": {"key": "value"}}],"tag": "action"}],"header": {"template": "wathet","title": {"content": "钉钉oapi接口测试任务执行报告通知","tag": "plain_text"}}}
}# payload = json.dumps({
# "msg_type": "post",
# "content": {
# "post": {
# "zh_cn": {
# "title": "钉钉oapi接口测试报告",
# "content": [
# [
# {
# "tag": "text",
# "text": f"【用例总数】:{total} \n"
# },
# {
# "tag": "text",
# "text": f"【测试通过】:{passed} \n"
# },
# {
# "tag": "text",
# "text": f"【测试失败】:{failed} \n"
# },
# {
# "tag": "text",
# "text": f"【测试错误】:{error} \n"
# },
# {
# "tag": "text",
# "text": f"【测试跳过】:{skipped} \n"
# },
# {
# "tag": "text",
# "text": f"【测试耗时】:{duration}s \n"
# },
# {
# "tag": "text",
# "text": f"【测试时间】:{build_time} \n"
# },
# {
# "tag": "text",
# "text": f"【测试结果】: {'通过~' if success else '失败!'}{chr(0x1f600) if success else chr(0x1f627)} \n"
# },
# {
# "tag": "a",
# "text": "Allure详细报告,请查看",
# "href": f"{report_url}"
# }
# ]
# ]
# }
# }
# }
# })
payload = json.dumps(card_demo)headers = {'Authorization': f"Bearer {response['tenant_access_token']}",'Content-Type': 'application/json'
}requests.request("POST", webhook, headers=headers, data=payload)# 单个报告,详细数据
# http://localhost:8080/job/auto_api_test/76/allure/data/test-cases/9a4eba68509440c8.jsonphone_mapping = {"zhang.san": "13421503860","li.si": "14564591649"
}
single_url = f"{report_url}/data/test-cases/"
for case in url_list:url = single_url + str(case["uid"]) + ".json"res = requests.get(url, auth=(USERNAME, TOKEN)).json()case["message"] = res["statusMessage"]
print("url_list", url_list)
author_list = list(set(jmespath.search("[*].author", url_list)))
# print("author_list",author_list)
failed_list = jmespath.search("[?status=='failed']", url_list)
print("failed_list", failed_list)
broken_list = jmespath.search("[?status=='broken']", url_list)
print("broken_list", broken_list)phone_list = []failed_string = f"<font color={b}>【**失败用例**】:\n</font>"
broken_string = f"<font color={c}>【**错误用例**】:\n</font>"for url_info in url_list:name_text = " " + url_info["name"] + "\n"url_text = "[" + " " + url_info["message"] + "]" + "(" + url_info["url"] + ")" + "\n"single_case_text = name_text + url_text# at_dict = {# "tag": "at",# "user_id": 1111,# }if url_info["status"] == "failed":failed_string += single_case_text# failed_string_list.append(url_dict)# failed_string_list.append(at_dict)elif url_info["status"] == "broken":failed_string += single_case_text# broken_string_list.append(name_dict)# broken_string_list.append(url_dict)# broken_string_list.append(at_dict)
null_string = " " + f"无\n"
if not failed_list:failed_string += null_string
if not broken_list:broken_string += null_string
# print("failed_string_list", failed_string_list, type(failed_string_list))
# print("broken_string_list", broken_string_list, type(broken_string_list))
# end_string_list = [{
# "tag": "a",
# "text": "Allure详细报告,请查看",
# "href": f"{report_url}"
# }]
all_string = failed_string + broken_string
# print("all_string", all_string_list, type(all_string_list))data_ca_demo = {"msg_type": "interactive","card": {"elements": [{"tag": "div","text": {"content": all_string,"tag": "lark_md"}}, {"actions": [{"tag": "button","text": {"content": "查看测试报告","tag": "lark_md"},"url": report_url,"type": "primary","value": {"key": "value"}}],"tag": "action"}],"header": {"template": "wathet","title": {"content": "钉钉oapi接口测试任务执行错误日志通知","tag": "plain_text"}}}
}
# data_ca = {
# "msg_type": "post",
# "content": {
# "post": {
# "zh_cn": {
# "title": "钉钉oapi接口测试报错信息汇总",
# "content": all_string_list
# }
# }
# }
# }
print("data_ca", data_ca_demo)
for author in author_list:if author in list(phone_mapping.keys()):phone_list.append(phone_mapping[author])
print(phone_list)response_r = requests.request("POST", webhook, headers=headers, data=json.dumps(data_ca_demo))
print("response_r", response_r.json())
4、执行上述脚本,查看飞书通知,如下
测试结果信息:

错误日志信息:

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走,希望可以帮助到大家!


相关文章:
Jenkins教程-10-发送飞书测试报告通知
上一小节我们学习了发送企业微信测试报告通知的方法,本小节我们讲解一下发送飞书测试报告通知的方法。 1、自动化用例执行完后,使用pytest_terminal_summary钩子函数收集测试结果,存入本地status.txt文件中,供Jenkins调用 conft…...
Swift开发——简单App设计
App的界面设计需要具有大量的图像并花费大量的时间,这样的应用不方便学习和交流,这里重点介绍SwiftUI界面元素的用法,通过简单App设计过程的讲解,展示图形用户界面应用程序的设计方法。 01、简单App设计 按照9.1节工程MyCh0901的创建方法,创建一个新的工程MyCh0902,此时工…...
Python操作mysql
一、python连接mysql 1.python连接mysql代码示例 from pymysql import Connection# 获取到mysql数据艰苦的连接对象 conn Connection(hostlocalhost,port3306,userroot,passwordroot ) # 打印mysql数据库软件信息 print(conn.get_server_info()) # 关闭到数据库的连接 conn.…...
监控易产品升级动态:V7.6.6.15版本全面升级
随着信息技术的不断发展,企业对系统监控和数据管理的需求日益增加。为了满足广大用户的实际需求,监控易团队经过不懈努力,成功推出了V7.6.6.15版本,对产品进行了全面升级和优化。本次升级不仅增强了产品的稳定性和可靠性ÿ…...
Vue3 + Element-plus + TS —— 动态表格自由编辑
前期回顾 《 穿越时空的代码、在回首:Evil.js两年后的全新解读 》-CSDN博客 Vue3 TS Element-Plus 封装Tree组件 《亲测可用》_ https://blog.csdn.net/m0_57904695/article/details/131664157?spm1001.2014.3001.5501 态表格 自由编辑 目录 ♻️ 效果图…...
虚拟机配置桥接模式
背景 因为要打一些awd比赛,一些扫描工具什么的,要用到kali,就想着换成一个桥接模式 但是我看网上的一些文章任然没弄好,遇到了一些问题 前置小问题 每次点开虚拟网络编辑器的时候都没有vmnet0,但是点击更改的时候却有vmnet0 第一步: 点击更改设置 第二步: 把wmnet0删掉 …...
星戈瑞DSPE-SS-PEG-CY7近红外花菁染料
DSPE-SS-PEG-CY7是一种具有复杂而精细结构的复合纳米材料,其在生物医学领域的应用增多。该材料结合了磷脂(DSPE)、聚乙二醇(PEG)、二硫键(SS)以及荧光染料(CY7)的特点&am…...
LeetCode:503. 下一个更大元素 II(Java 单调栈)
目录 503. 下一个更大元素 II 题目描述: 实现代码与解析: 单调栈 原理思路: 503. 下一个更大元素 II 题目描述: 给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] )&…...
代码重构:解读重构概念及重构实战
目录 一.重构是什么(what) 1.重构的本质 2.重构≠性能优化 二.重构的目的(why) 1.去写好的代码 2.去写更灵活的代码 三.重构的时机(when and where) 1.何时重构 2.何时不重构 四.重构的方法(how) 1.重构关键核心 2.重构方法 3.重构工具 小结 一.重构是什么(what)…...
java.util.Optional类介绍
java.util.Optional 是 Java 8 引入的一个容器类,用于表示可能包含或不包含非空值的对象。它的设计初衷是为了减少程序中的空指针异常(NullPointerException),并使代码更加简洁和易读。 Optional 类的介绍 1. 特点 避免显式的 null 检查:使用 Optional 可以避免显式的 n…...
PhotoShop自动生成号码牌文件
1、说明 设计卡牌的时候,遇到自动生成编号,从01500到-02500,一个一个的手写,在存储保存成psd格式的文件,会很耗时。 下面将介绍如何使用ps自动生成psd格式的文件 2、使用excle生成数字 从01500到-02500 第一步&…...
02逻辑代数与硬件描述语言基础
2.1 逻辑代数(简单逻辑的运算) 2.2 逻辑函数的卡诺图(从图论的角度)化简法 2.3 硬件描述语言Verilog HDL基础(研究生阶段才用得到) 要求: 1、熟悉逻辑代数常用基本定律、恒等式和规则。 2、掌握…...
OpenGL3.3_C++_Windows(21)
抗锯齿 遇到模型边缘有锯齿:光栅器将顶点数据转化为片段的方式有关 抗锯齿:产生更平滑的边缘SSAA超采样抗锯齿:使用比正常分辨率更高的分辨率,来渲染场景,它也会带来很大的性能开销。 光栅器: 位于最终处…...
clickhouse学习
ClickHouse学习 安装部署 1.下载rpm文件 下载地址:https://packages.clickhouse.com/rpm/stable/ clickhouse-client-23.2.1.2537.x86_64.rpm clickhouse-common-static-23.2.1.2537.x86_64.rpm clickhouse-common-static-dbg-23.2.1.2537.x86_64.rpm clickhous…...
MySQL高级-索引-使用规则-前缀索引
文章目录 1、前缀索引2、前缀长度3、查询表数据4、查询表的记录总数5、计算并返回具有电子邮件地址(email)的用户的数量6、从tb_user表中计算并返回具有不同电子邮件地址的用户的数量7、计算唯一电子邮件地址(email)的比例相对于表…...
外星生命在地球的潜在存在:科学、哲学与社会的交织
外星生命在地球的潜在存在:科学、哲学与社会的交织 摘要:近年来,关于外星生命是否存在的讨论日益激烈。有研究表明,外星人可能已经在地球漫步,这一观点引发了广泛的科学、哲学和社会学思考。本文将从科学角度探讨外星…...
使用FRP 0.58版本进行内网穿透的详细教程
什么是FRP? FRP(Fast Reverse Proxy)是一款高性能的反向代理应用,主要用于内网穿透。通过FRP,您可以将内网服务暴露给外网用户,无需进行复杂的网络配置。 准备工作 服务器:一台具备公网IP的服…...
0000电子技术基础概述
数电 未来课的基础 以前是模块、器件级 现在是 系统级 价格、性能、 技术更新快速的好处:得到了实惠 坏处:工程师需要不断地学习,不变就容易out,要用发展的眼光看待问题 了解基础知识、还要有前沿概念。 理论课、实践课要相结…...
vscode+platformio使用STC官方库进行51单片机开发 -- 中断异常
问题描述 在进行STC8H1K08单片机的开发时,使用官方提供的C语言库函数,在vscodeplatformio开发环境下发现库函数的串口中断异常,看起来像是中断没有触发。 解决过程 用串口中断时一直没有触发中断,起初没有怀疑是中断的问题&…...
探索Android架构设计
Android 应用架构设计探索:MVC、MVP、MVVM和组件化 MVC、MVP和MVVM是常见的三种架构设计模式,当前MVP和MVVM的使用相对比较广泛,当然MVC也并没有过时之说。而所谓的组件化就是指将应用根据业务需求划分成各个模块来进行开发,每个…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10pip3.10) 一:前言二:安装编译依赖二:安装Python3.10三:安装PIP3.10四:安装Paddlepaddle基础框架4.1…...
软件工程 期末复习
瀑布模型:计划 螺旋模型:风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合:模块内部功能紧密 模块之间依赖程度小 高内聚:指的是一个模块内部的功能应该紧密相关。换句话说,一个模块应当只实现单一的功能…...
