我把大模型接进CI后,失败用例自动归因,报告直接发飞书

简介: 本文介绍一种AI驱动的CI失败归因方案:通过大模型自动分析pytest失败日志,精准分类环境问题、用例缺陷与代码Bug,并将结构化结论实时推送至飞书。测试排查耗时从2小时降至10分钟,效率提升12倍,全部脚本开源可复用。

以前CI红了,一群人围在屏幕前翻日志猜原因。现在CI红了,飞书里直接弹出一条消息:“本次失败50条,其中45条为环境问题,5条疑似代码Bug,详情见下。”

大家好,我是某互联网公司的测试架构师。

上个月,团队里一个做自动化的同事跟我吐槽:“哥,我每天花在‘看失败报告’上的时间,比写测试脚本还多。”

我问他怎么回事。他说每天CI跑完,少则二三十条失败,多则上百条。点开一看,有的是环境超时,有的是元素定位失效,有的是测试数据被其他用例污染了,真正是Bug的可能就三五条。但为了找出那三五条,他得一条条翻日志、看截图、对代码变更,两三个小时就没了。

我说:“你让大模型替你看。”

他问:“怎么看?”

我说:“CI跑完,把失败日志丢给大模型,让它做归因分类,然后把结论发到飞书。你只看结论。”

两天后,他的CI流水线里多了一个步骤。现在CI跑完,飞书群里直接弹出一条消息:

本次回归失败50条。

环境问题:42条(连接超时/容器启动失败)
用例问题:3条(定位器失效/数据污染)
疑似Bug:5条(断言失败,涉及订单状态不一致) 详情:点击查看完整报告
他跟我说:“我现在每天花在失败排查上的时间,从两小时变成了十分钟。”

今天这篇文章,我把整个方案拆开讲。从CI配置到Prompt模板到飞书机器人,全部可复制。

一、先搞清楚:这个方案的核心逻辑是什么?
传统CI流水线的测试阶段是这样的:

代码提交 → 触发CI → 跑测试 → 生成报告 → 人工看报告 → 人工归因 → 决定下一步
人工看报告、人工归因,是整个链路里最耗时的环节。

我们的方案是在“生成报告”和“人工看报告”之间,插进去一个AI归因节点:

代码提交 → 触发CI → 跑测试 → 生成原始报告 → AI归因分析 → 生成结构化结论 → 飞书推送
AI归因节点做三件事:

收集失败用例的日志、截图路径、相关代码变更
调用大模型对每条失败进行分类(环境/用例/代码Bug/数据/未知)
汇总成结构化报告,通过飞书机器人推送
人工只需要看AI筛出来的“疑似Bug”那几条。

二、准备工作:你需要什么?
环境要求:

一个跑在CI上的测试项目(我们用的是GitLab CI + pytest)
一个飞书群,并创建自定义机器人(拿到webhook地址)
一个大模型API Key(DeepSeek、GLM、通义千问都行,我们用的DeepSeek)
测试框架输出:pytest 建议用 --junitxml=report.xml 生成结构化报告。或者用 pytest-json-report 插件生成JSON,解析更方便。

飞书机器人:在飞书群设置里添加“自定义机器人”,拿到webhook地址,安全设置选“签名校验”或“IP白名单”。我们用的签名校验,后面代码里会体现。

三、核心脚本:失败归因分析器
整个方案的核心是一个Python脚本,放在项目根目录的 ci/analyze_failures.py。

!/usr/bin/env python3

-- coding: utf-8 --

import json
import os
import sys
import xml.etree.ElementTree as ET
from openai import OpenAI

============ 配置 ============

DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY")
FEISHU_WEBHOOK = os.environ.get("FEISHU_WEBHOOK")
FEISHU_SECRET = os.environ.get("FEISHU_SECRET")

client = OpenAI(
api_key=DEEPSEEK_API_KEY,
base_url="https://api.deepseek.com"
)

============ 解析pytest报告 ============

def parse_junit_xml(xml_path):
"""从JUnit XML中提取失败用例信息"""
tree = ET.parse(xml_path)
root = tree.getroot()
failures = []
for tc in root.iter("testcase"):
failure = tc.find("failure")
error = tc.find("error")
if failure isnotNoneor error isnotNone:
node = failure if failure isnotNoneelse error
failures.append({
"classname": tc.get("classname"),
"name": tc.get("name"),
"time": tc.get("time"),
"message": node.get("message", ""),
"text": (node.text or"")[:2000], # 截断,避免token爆炸
})
return failures

============ 收集上下文 ============

def collect_context(failures):
"""收集Git变更、环境信息等上下文"""
import subprocess
try:
git_diff = subprocess.check_output(
["git", "diff", "HEAD~1", "--stat"],
stderr=subprocess.DEVNULL
).decode("utf-8", errors="ignore")
except Exception:
git_diff = "无法获取Git变更信息"

return {
    "failures": failures,
    "git_diff": git_diff[:3000],
    "ci_env": os.environ.get("CI_ENVIRONMENT", "unknown"),
    "timestamp": os.environ.get("CI_PIPELINE_CREATED_AT", ""),
}

============ 调用大模型归因 ============

PROMPT_TEMPLATE = """你是一个资深的测试开发工程师,正在分析CI流水线中失败的测试用例。

请对下面每一条失败用例进行归因分类,只能选择以下类别之一:

  • 环境问题:网络超时、容器启动失败、依赖服务不可用、配置错误等
  • 用例问题:定位器失效、测试数据污染、用例逻辑错误、断言过强/过弱
  • 代码Bug:业务逻辑错误、接口返回不符合预期、状态不一致等
  • 数据问题:测试数据不存在、数据被其他用例修改、数据过期
  • 未知:无法判断

同时请给出:

  1. 一句话归因结论
  2. 置信度(高/中/低)
  3. 如果是代码Bug,给出建议的复现步骤

输入数据:
{context}

请严格输出JSON数组,每个元素包含:
{ {
"name": "用例名称",
"category": "环境问题|用例问题|代码Bug|数据问题|未知",
"conclusion": "一句话结论",
"confidence": "高|中|低",
"reproduce_steps": "如果是代码Bug,给出复现步骤,否则为空字符串"
}}
只输出JSON,不要解释。
"""

def analyze_with_llm(context):
prompt = PROMPT_TEMPLATE.format(
context=json.dumps(context, ensure_ascii=False, indent=2)
)
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": prompt}],
temperature=0.1,
response_format={"type": "json_object"}
)
content = resp.choices[0].message.content
return json.loads(content)

============ 生成飞书报告 ============

def build_feishu_card(results, total_failures):
"""构建飞书消息卡片"""
from collections import Counter
counter = Counter(r["category"] for r in results)
bugs = [r for r in results if r["category"] == "代码Bug"]

# 按类别统计
summary_lines = []
for cat in ["环境问题", "用例问题", "代码Bug", "数据问题", "未知"]:
    count = counter.get(cat, 0)
    if count > 0:
        summary_lines.append(f"**{cat}**:{count}条")

# 疑似Bug详情
bug_lines = []
for b in bugs[:5]:  # 最多展示5条
    bug_lines.append(f"- `{b['name']}`:{b['conclusion']}(置信度:{b['confidence']})")
ifnot bug_lines:
    bug_lines.append("无")

card = {
    "msg_type": "interactive",
    "card": {
        "header": {
            "title": {"tag": "plain_text", "content": "🔴 CI回归失败归因报告"},
            "template": "red"
        },
        "elements": [
            {"tag": "div", "text": {"tag": "lark_md", "content": f"**失败总数**:{total_failures}条\n\n" + "\n".join(summary_lines)}},
            {"tag": "hr"},
            {"tag": "div", "text": {"tag": "lark_md", "content": "**疑似代码Bug**:\n" + "\n".join(bug_lines)}},
            {"tag": "hr"},
            {"tag": "note", "elements": [{"tag": "plain_text", "content": "由AI自动归因,请人工复核疑似Bug项"}]}
        ]
    }
}
return card

============ 发送飞书消息 ============

def send_feishu(card):
import time, hmac, hashlib, base64
timestamp = str(int(time.time()))
string_to_sign = f"{timestamp}\n{FEISHU_SECRET}"
hmac_code = hmac.new(string_to_sign.encode("utf-8"), digestmod=hashlib.sha256).digest()
sign = base64.b64encode(hmac_code).decode("utf-8")

import requests
payload = {
    "timestamp": timestamp,
    "sign": sign,
    "msg_type": "interactive",
    "card": card["card"]
}
resp = requests.post(FEISHU_WEBHOOK, json=payload)
print(f"飞书发送结果:{resp.status_code} {resp.text}")

============ 主流程 ============

def main():
report_path = "report.xml"
ifnot os.path.exists(report_path):
print("未找到报告文件,退出")
return

failures = parse_junit_xml(report_path)
ifnot failures:
    print("无失败用例,无需归因")
    return

context = collect_context(failures)
results = analyze_with_llm(context)
card = build_feishu_card(results, len(failures))
send_feishu(card)
print("归因完成")

if name == "main":
main()
四、接入CI:GitLab CI示例
在 .gitlab-ci.yml 里增加一个 stage:

stages:
-test
-analyze

run-tests:
stage:test
script:
-pipinstall-rrequirements.txt
-pytest--junitxml=report.xml
artifacts:
when:always
paths:
-report.xml
expire_in:1week

ai-analyze:
stage:analyze
needs:["run-tests"]
script:
-pipinstallopenairequests
-pythonci/analyze_failures.py
variables:
DEEPSEEK_API_KEY:$DEEPSEEK_API_KEY
FEISHU_WEBHOOK:$FEISHU_WEBHOOK
FEISHU_SECRET:$FEISHU_SECRET
only:
-merge_requests
-main
关键点:artifacts 必须把 report.xml 传递到下一个stage。when: always 确保测试失败时也会生成报告。

五、实际效果:从两小时到十分钟
我们拿一个跑了3个月的订单中台项目做对比。

改造前:

CI跑完,测试同学打开报告,50条失败
逐条点开,看日志、看截图、对代码
平均每条排查2-3分钟,总计约2小时
最终确认:5条真Bug,45条环境/用例问题
改造后:

CI跑完,飞书收到AI归因报告
测试同学只看“疑似代码Bug”那5条
人工复核5条,每条1-2分钟,总计约10分钟
其余45条自动分类,不占用注意力
效率提升:12倍。

六、避坑指南
坑一:把AI归因当成最终结论。

AI归因是“预筛”,不是“判决”。我们永远在飞书卡片末尾加一句“请人工复核疑似Bug项”。AI负责把噪音过滤掉,你负责做最终判断。

坑二:日志截断太狠,AI看不到关键信息。

一开始我只给AI传了失败消息的前200个字符,结果它经常把“连接超时”误判为“代码Bug”。后来改成传完整failure text的前2000字符,准确率明显提升。

坑三:Prompt写得太模糊。

如果你只写“分析这些失败”,AI会给你一堆模棱两可的结论。必须明确分类选项(环境/用例/代码Bug/数据/未知),并强制输出JSON。分类选项本身就是对AI的约束。

坑四:Token成本失控。

50条失败,每条2000字符,加上Git diff和Prompt模板,一次调用大概消耗3-5万Token。DeepSeek的价格,一次不到一毛钱。但如果你每天跑几十次CI,成本会累积。建议只在MR和main分支触发,feature分支不跑归因。

坑五:忽略安全。

失败日志里可能包含敏感信息(接口地址、Token、用户数据)。在传给大模型之前,做一个简单的脱敏处理:正则替换掉Bearer Token、手机号、邮箱、IP地址。这一步不能省。

最后
CI跑完测试,不是终点。把“失败归因”也自动化,才是终点。

以前你面对50条失败,要一条条翻。现在你面对一条飞书消息,只看5条。

你不需要成为大模型专家。你只需要在CI里加一个节点,让AI替你做那80%的噪音过滤。

下次CI红了的时候,别打开日志了。让飞书给你发结论。

相关文章
|
2天前
|
人工智能 缓存 运维
全量跑不动、人肉挑不完:代码量涨8倍之后,测试选择策略该先改哪一步
本文剖析Anthropic因AI编码爆发导致CI测试分析系统(TIA)崩溃的真实案例:Claude贡献80%合并代码,CI任务半年激增25倍,传统“单写有状态”TIA架构率先崩塌。文章详述三次线性补丁失效过程,揭示指数增长下容量规划的底层陷阱,并给出轻量级、可落地的“journal式”重构方案——状态持久化、写入无状态、读取归并视图,助力中型团队低成本构建弹性CI测试选择能力。
|
21小时前
|
人工智能 供应链 架构师
为什么你们团队的AI测试没效果?缺的不是工具
本文揭示AI测试失败的根源:非工具之过,而在目标错位。作者指出三大症结——缺验收标准、止步“生成”、工具驱动而非问题驱动,并提出三步解法:定义量化指标、构建闭环链路、坚持问题导向。工具只是放大器,真正关键在于清晰的问题意识与工程化能力。
|
2天前
|
JSON 人工智能 测试技术
Agent Skills、MCP、Function Calling 到底啥区别?一文讲透
本文厘清AI Agent三大核心概念:Function Calling(模型调用工具的“嘴”,负责结构化指令)、MCP(标准化连接协议, akin “插座”,解耦模型与工具)、Agent Skills(封装业务逻辑的“经验包”,含流程、异常处理与复用能力)。三者呈层级协作关系,非替代关系,共同构建可落地的自动化测试体系。
|
6天前
|
存储 安全 测试技术
登录功能测试用例怎么答出层次:4 个提问 + 五层展开 + 1 分钟【推断】优先级收口
本文揭秘测试岗高频面试题“手写登录测试用例”的底层逻辑:不考条数,而考结构化思维。强调动笔前先问4个关键边界问题,再按功能→异常→安全→兼容→性能五层有序展开,最后以优先级收口。附可落地的5分钟时间分配法与参数化代码实践,助你从背模板跃升为能自主铺开测试范围的专业 tester。
|
12天前
|
SQL 测试技术 数据库连接
我用ChatGPT把回归测试从3天压到3小时,提示词全公开
本文分享如何用ChatGPT优化电商后台回归测试:通过提示词引导,实现用例梳理、自动化脚本生成与日志分析三步提效,将3天人工测试压缩至3小时。强调其辅助定位而非替代人力,聚焦释放工程师精力于高价值工作。
|
4天前
|
人工智能 JavaScript API
阿里云百炼Qwen3.8‑Omni‑Flash模型能力拆解:免费100万Tokens
阿里云发布Qwen3.8‑Omni‑Flash——原生全模态大模型,支持1M长序列及音视频理解、生成与编辑、视频问答等Agentic能力;免费体验100万Tokens,兼容OpenAI协议,开箱即用。(239字)
133 1
|
8天前
|
人工智能 自然语言处理 测试技术
Google 开源 ARTEMIS:AI Agent 如何接管 Android 真机测试?
Google开源的ARTEMIS是一款AI驱动的Android自动化测试框架,支持自然语言指令、跨App长流程操作、多模态控件识别(Accessibility+OCR+视觉模型),原生集成MCP协议,可无缝接入Antigravity等AI编程环境,实现“描述目标→自主规划→真机执行→结果分析”闭环,标志着移动端测试迈向AI Agent时代。
|
8天前
|
SQL 人工智能 安全
Agent Harness 又要多一层?Jev 开始接管这些高频判断
Jev作为新型System One Model,专司Agent中高频、明确的判断任务(如工具路由、技能筛选、上下文过滤、安全守门与执行复核),将LLM从繁重决策中解放,推动Agent架构向“规则+决策模型+LLM+工具”多层协同演进。
|
14天前
|
人工智能 自然语言处理 前端开发
字节用半年让85%的AI用例跑进CI/CD,你的团队还在为“AI生成不能用”发愁?
本文剖析字节跳动NL2Test Agent成功落地的五大关键:聚焦“用例转译”而非替代、先闭环再优化、LLM与程序分工协作、精准治理上下文、优先生成稳定断言。对比失败案例,揭示AI测试成败核心在工程设计,而非模型能力。
|
24天前
|
人工智能 算法
3个月,520万播放,6666个粉丝,普通人如何用AI搞副业?
AI时代,普通人也能轻松做自媒体!本文揭秘“AI自动变现”全流程:从0搭建账号、AI批量生产内容、多平台自动分发,到广告/带货/IP多元变现。无需天赋团队,7天起号,日更3-5条,小投入撬动长期收益。方法已验证,人人可复制。

热门文章

最新文章