找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3323

积分

0

好友

469

主题
发表于 12 小时前 | 查看: 3| 回复: 0

上一篇文章发布后,收到了一些关于“不看代码怎么保证质量”的讨论。我想说的是,当你能够熟练运用Claude Opus 4.5或同级别模型,并同时推进多个实际项目时,你会发现“写代码”本身正在快速贬值。

工作九年,我对编码、架构和故障排查能力有清晰的认知。亲手实现功能、定位隐蔽Bug、成功重构项目带来的成就感,确实令人兴奋。但时代在向前,固步自封的程序员终将被淘汰。程序员是一个需要终身学习的职业,它不会被AI替代,但会无情地淘汰那些止步不前的人。

背景:三人团队,如何保障每小时七万次告警的稳定性?

我目前正带领一个三人小组,负责重构公司的告警平台(基于夜莺二次开发)。项目已进入内测阶段,但一个现实的问题摆在我们面前:没有专职的测试人员,仅靠我一人Review代码来兜底质量,这几乎是不可完成的任务。

从一月份起,我的工作重心转向代码架构重构,核心目标是让AI编码更精准、更可控。为此,我们引入了OpenSpec规范、严格的编码约束、单元测试要求,并编写了辅助线上排查的AI Skill。

目前,平台仅接入了9个项目组,每小时告警规则的执行量已达7万次。想象一下,当公司百余个项目组全部接入后,必然会频繁出现“告警没触发,帮忙查一下”的求助。三人团队无力应对此类琐碎却消耗人力的答疑工作。

我们的解决方案是:让用户能清晰地感知告警执行的每一步。于是,“调度日志”功能被提上日程。令人惊讶的是,从需求分析、存储架构设计到编码实现,仅用了一周时间,两人便完成了初版上线。这,正是AI工程化赋能后所展现的交付能力。

整个项目代码量约三万行。通读并理解这三万行代码?这不现实。告警系统的稳定性至关重要,我必须找到一个可靠的机制,确保每个上线功能都“能用”,且不会引发稳定性问题。因此,我将目光投向了测试领域,这是我职业生涯中亟待补全的一环。

演进之路:从单元测试到黑盒集成测试

第一步:让代码变得“可测试”

在工程化演进中,我们采用了DDD结合TDD的绞杀模式,对现有代码进行渐进式重构。大家都熟悉测试金字塔,单元测试是基石。有人会质疑:AI写的代码,再由AI写单元测试,能靠谱吗?

换个思路:单元测试的首要目的,未必是验证业务百分百正确,而是确保代码本身是“可测试的”。它迫使你写出结构清晰、依赖明确、便于隔离的代码,而非一堆难以测试的“泥球”。当发现问题时,让AI补充对应场景的单元测试,代码的稳定性便会逐步提升。因此,在我们的OpenSpec规范中,所有后端代码都必须附带单元测试。

测试金字塔结构示意图

探索过程:为什么UI自动化测试不是最优解?

仅有单元测试不足以验证复杂的端到端业务场景。为此,我花费了大半个月时间,深度实验了三种方案:

  1. 后端集成测试 —— 通过API验证完整业务流程。
  2. UI端到端测试 —— 使用Python + Playwright进行UI自动化。
  3. AI + MCP验收 —— 让AI直接操作浏览器并验证数据库状态。

最终结论是:后端集成测试为主,辅以AI/MCP验收,是最优组合。 UI自动化测试为何被放弃?其准确率偏低,维护成本高昂。AI生成的Playwright脚本命中率不稳定,且前端UI的频繁变动会导致测试用例大量失效,投入产出比不佳。

核心方案:基于API的黑盒集成测试

我们最终选用了Ginkgo测试框架。每次测试启动时,都会重建数据库、初始化基础数据,然后完全通过调用API来构建场景、触发业务并验证结果。

集成测试流程架构图

几个关键设计问题的解答:

Q:如何真实触发告警?
测试会完整编译并启动中心服务与告警引擎。告警规则、监控指标数据均由测试代码构造,并按照生产环境的标准流程——将指标数据推送到VictoriaMetrics,触发告警引擎评估规则,进而验证整个告警链路。

Q:测试结束后为何不清理数据?
有两个考量:一是担心API构造场景可能存在偏差,保留数据便于手动二次核查关联关系;二是为后续使用AI Skill进行自动化验收预留空间。

必须遵守的黑盒测试原则

这是整个方案最核心的纪律,必须严格执行:

规则 说明
数据创建必须通过API 用户、用户组、业务组、策略、规则等所有前置数据,必须调用中心服务API创建,严禁直接操作数据库。
数据验证优先通过API 验证系统状态时,优先调用查询API,而非直接读取数据库。
业务流程触发通过API 推送指标、确认事故、解决事故等所有业务操作,必须通过API完成。
禁止硬编码假数据 禁止使用假ID(如 RuleId: 1001)直接插入数据库,必须使用API返回的真实ID。

为何要如此严格?
集成测试的目的是验证整个系统的端到端行为。直接操作数据库会绕过API层的参数校验、权限控制和核心业务逻辑。使用假数据则无法保障数据关联的完整性,可能导致测试场景与生产环境脱节,失去验证价值。

正确与错误测试数据构造方式对比图

实战:一个P0级测试用例剖析

理论说再多,不如看代码。以“告警触发后未响应,系统应自动创建事故并逐级升级”这个P0场景为例,其用户故事和验证步骤设计如下:

完整升级流程测试步骤设计图

对应的测试代码结构大致如下(使用Go语言和Ginkgo框架):

var _ = Describe("P0: FullEscalationFlow", Label("P0"), Ordered, func() {
    BeforeAll(func() {
        // 从共享上下文获取场景数据(已通过 API 创建)
        scenario = sharedCtx.Scenarios["s_full_escalation"]
        testTags = scenario.UniqueDefaultTags()
        metricName = scenario.UniqueMetric("full_esc")
    })

    Context("步骤1: 推送告警指标", func() {
        It("推送告警指标触发告警规则", func() {
            // 推送 5 次指标,每次间隔 3 秒
            err := localAPI.PushMetricsContinuously(metricName, 100, testTags, 5, 3*time.Second)
            Expect(err).NotTo(HaveOccurred())
        })

        It("应该自动创建 Incident", func() {
            // 等待 Incident 创建(最多 90 秒)
            Eventually(func() int64 {
                incidents, _ := localAPI.ListIncidents(api.ListIncidentsParams{
                    GroupID: scenario.BusiGroupID,
                })
                // ... 查找未解决的 Incident
            }, 90*time.Second, 5*time.Second).Should(BeNumerically(">", 0))
        })
    })

    Context("步骤3: 等待 L1→L2 升级", func() {
        It("应该升级到 L2", func() {
            Eventually(func() int {
                incident, _ := localAPI.GetIncident(incidentID)
                return incident.CurrentEscalationLevel
            }, 75*time.Second, 5*time.Second).Should(BeNumerically(">=", 2))
        })
    })

    // ... 后续步骤
})

这段代码体现了几个关键点:

  1. 场景数据通过API创建:在 BeforeAll 中使用的数据,是预先通过调用真实API创建的,绝非硬编码的假数据。
  2. 使用 Eventually 等待异步结果:对于告警触发、状态变更等异步操作,使用 Eventually 进行轮询等待,而非写死的 Sleep,使测试更健壮。
  3. 每个 It 验证一个业务预期:测试聚焦于“系统应该做什么”,而不是“代码如何实现”。
  4. 完善的失败诊断:测试失败时,会自动收集事故状态、通知记录、时间线等上下文信息,便于快速定位。

测试分层与执行策略

我们将测试用例按优先级分为三层,平衡了反馈速度与覆盖广度:

优先级 场景示例 运行时机
P0 完整升级流程、确认后停止升级 每次代码提交
P1 最大升级次数限制、策略优先级、自动恢复、通知验证 每日定时构建
P2 边界条件、异常处理、基础CRUD操作 发布前回归测试

通过Ginkgo的标签过滤功能,可以灵活选择执行范围:

# 仅快速验证核心P0流程(约5分钟)
go test ./tests/integration/duty/... -v --ginkgo.label-filter="P0"

# 执行P0+P1,进行完整功能验证(约15分钟)
go test ./tests/integration/duty/... -v --ginkgo.label-filter="P0 || P1"

AI如何赋能测试调试与维护

当测试失败时,我的排查工作流已经高度依赖AI协同:

测试失败排查与修复流程图

具体操作时,我会将终端报错信息和日志文件路径直接抛给AI,指令如下:

“P0测试在第180行失败,相关日志在 logs/alert.log,请帮我分析根本原因。”

AI会主动去搜索 workers_createdincident createdescalation 等关键日志,快速定位问题究竟是规则未加载、事故未创建,还是升级逻辑未触发。这套流程下来,从发现测试失败到定位并开始修复,通常只需3到5分钟。

思考:AI时代,程序员角色的必然演进

不知你是否意识到,程序员的核心价值在于完整交付一个可用的需求。在AI普及之前,编码占据了大量体力劳动,导致测试环节常常需要他人分担(即便简单的P0场景,开发者也需手动验收)。正因为编码是重体力活,很少有人愿意再额外编写自动化验收脚本。

但AI时代改变了这一切。我们小组的成员现在需要全面承担设计、编码、测试、运维的职责。这并非内卷,而是后端工程师面向未来必须完成的角色演进。

AI时代与传统软件开发模式对比图

具体职责转变如下:

  • 设计:与AI充分对齐架构与需求细节,你必须预判AI会如何实现。
  • 编码:将具体的实现任务交给AI。
  • 测试:遵循黑盒测试原则,由于你不深入代码细节,你的职责是对功能、场景和最终结果负责。
  • 运维:你能阅读的代码和查询的数据,AI同样可以。关键在于将你的排查手段流程化、工具化,赋能给AI,让它比你更快地执行。

至于更高阶的系统规划、架构演进等依赖深厚经验的工作,其价值将愈发凸显。

总结与展望

这套集成测试体系运行近一个月,带来了几点深刻体会:

  1. 测试是信心的来源:无需过度焦虑AI生成的代码是否有隐患,测试通过即代表功能可用。
  2. 场景驱动优于盲目追求覆盖率:与其执着于达到80%的代码覆盖率,不如切实保障所有核心业务场景(P0)的畅通无阻。
  3. AI是高效的调试伙伴:将日志分析、关键词检索、根因定位等繁琐工作交给AI,能极大提升排查效率。
  4. 测试代码同样需要工程化:测试代码也是软件产品的一部分,需要良好的抽象、设计和持续重构,切忌写成难以维护的“一次性脚本”。

如今的Claude Opus 4.5,其能力大约只是一年前Sonnet 3.5的水平。试想一下,未来模型的进化速度又将如何?

焦虑吗?我已度过那个阶段。现在,我更多的感受是:只要你有好的想法,就有能力将其实现。我庆幸自己在职业生涯中,涉足了规划、设计、编码、运维、架构演进的方方面面。记得几年前面试时,我曾问对方:“你们的服务跑在哪里?上线流程是怎样的?”得到的回答是:“点一键发布就行,做业务不需要关心这些。”

时代真的变了。

(最后,谁能保证代码Review后,就100%没有Bug呢?)




上一篇:深入解读Anthropic“混乱理论”:AI长程推理为何会走向不可预测的失败
下一篇:Python代码防御奇招:通过库名乱导实现“我能跑,你不行”的逆向保护
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-7 21:39 , Processed in 0.418252 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表