Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

5.5 CI流水线——从push到报告全自动

那台不知疲倦的机器人

上午十点半,你写完一个新的传感器校准函数,推送到Git仓库。你端起水杯正准备去茶水间,口袋里手机震了一下。

eng-lite CI: Build #142 Passed ✅ Build (28s) ✅ Static Analysis (12s) ✅ Unit Tests (18s) ✅ Coverage Check (97.5% → OK) ✅ Traceability Check (all reqs mapped)

你甚至没有回到座位。你继续走向茶水间,心里很平静。

这是CI真正的力量:让你在不工作的时候也能确信工作是正确的。 那台在云端默默运行的Ubuntu容器,在过去的2分15秒里做了你需要在本地手动执行的七个步骤,而且绝不会因为“赶着下班“而跳过其中任何一步。

另一天,你的同事Alex推送了一个改动。30秒后,手机响了,这次是红的:

eng-lite CI: Build #143 FAILED ❌ Static Analysis: src/sensor.c:47 (uninitvar)

Alex叹了口气,但立刻知道问题在哪。他在sensor_filter里声明了一个局部变量但忘了赋值。如果没有CI,这个缺陷可能要到集成测试阶段才被发现,那时定位成本可能是现在的10倍。

CI的本质不是自动化,是反馈速度。反馈越早,修复成本越低。 这个概念在汽车行业有精确的术语支撑:根据Automotive SPICE和ISO 26262的最佳实践,缺陷修复成本随发现阶段呈指数增长。在编码阶段发现并修复一个缺陷的成本是1x,在系统测试阶段是10x,在客户车辆上市后是100x甚至更高。

核心洞察:CI的本质不是自动化,而是反馈速度——反馈越早,修复成本越低。 缺陷修复成本随发现阶段指数增长:编码阶段1x,系统测试10x,上市后100x。CI让你“甚至不用回到座位“就能确信工作正确,也让同事那个忘赋值的局部变量在30秒内现形,而不是流到集成测试阶段再花10倍代价定位。自动化只是手段,把反馈压缩到提交后几分钟才是目的。

走进eng-lite的CI配置

.github/workflows/ci.yml

name: eng-lite CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc cppcheck
          pip3 install --user gcovr
          sudo gem install ceedling -v 0.31.1

      - name: Build
        run: make all

      - name: Static Analysis
        run: make check

      - name: Unit Tests
        run: make test

      - name: Coverage
        run: make coverage

      - name: Traceability Check
        run: make trace

      - name: Upload Coverage Report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: build/coverage/

      - name: Upload Traceability Matrix
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: traceability-matrix
          path: build/traceability.csv

  # TEACHING: In production, add a HIL job here that:
  #   1. Cross-compiles the firmware for the target ECU
  #   2. Deploys the ELF to a HIL rack
  #   3. Runs the smoke-test suite on real hardware
  #   4. Reports results back to this CI dashboard

Pipeline Stages解析

这条流水线包含6个串行阶段,前一个失败则后一个不执行:

Stage 0: Install Dependencies(环境准备) 在GitHub Actions提供的Ubuntu容器中安装必要的工具。/* TEACHING: 生产项目的CI容器通常使用预构建的Docker镜像,包含完整的交叉编译工具链。*/

Stage 1: Build(编译) 运行make all。这是最基础的检查:代码能不能编译,有没有语法错误。

Stage 2: Static Analysis(静态分析) 运行make check(即scripts/check.sh)。返回非零则流水线失败。这是自动化门禁的第一道真门槛:编译通过不代表代码安全。

Stage 3: Unit Tests(单元测试) 运行make test(Ceedling + Unity)。所有测试用例必须通过。

Stage 4: Coverage(覆盖率) 生成覆盖率报告。在eng-lite中,这一步不设失败阈值(教学宽容)。在生产项目中,通常会设置覆盖率阈值并作为门禁。

Stage 5: Traceability Check(追溯检查) 运行make trace,检查所有@req标注的需求是否都有对应测试。若有未覆盖需求,流水线失败。

Concurrency Group

eng-lite的CI配置中还使用了concurrency控制:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

这意味着如果同一个分支上有新的push,正在运行的旧流水线会被取消。这节省了CI运行资源,避免了排队等待。

核心洞察:一条健康的CI流水线由六个串行阶段构成,前一步失败后一步绝不执行。 从装依赖到构建、静态分析、单元测试、覆盖率、追溯,每道门各司其职,其中追溯阶段刻意放在测试之后——它的前提是测试文件真实存在,语义是“所有测试通过后,再检查每个需求是否都有测试“;concurrency取消同分支的旧流水线,省下CI资源、避免排队等待。

从教学到生产:HIL测试的集成

eng-lite的CI是纯软件验证。在真实的汽车项目中,CI流水线还需要集成硬件在环(HIL)测试。以下是HIL测试如何挂接到这条流水线上的思路:

# 概念性扩展(eng-lite不包含此部分)
  - name: HIL Smoke Test
    if: github.ref == 'refs/heads/release'
    run: |
      scp build/bin/firmware.elf testbench@hil-rack-03:/deploy/
      ssh testbench@hil-rack-03 "hil_runner --suite=smoke --timeout=300"

HIL测试的基本流程:

  1. 编译固件并部署到HIL机柜
  2. HIL仿真器提供模拟的传感器信号(油门踏板、制动踏板、转向角等)
  3. 固件运行在真实的ECU上,接收仿真信号,输出执行器命令
  4. 测试脚本验证执行器命令是否符合预期
  5. 结果回传CI系统

HIL测试通常是CI流水线中最慢的阶段(可能需要几小时),因此往往只在特定分支(如release分支)上触发。

核心洞察:纯软件CI与HIL验证的分界,对应着从“逻辑正确“到“硬件正确“的证据跨越。 eng-lite的CI只做宿主机上的软件验证;生产项目则需挂接HIL:编译固件部署到机柜、仿真器喂入油门/制动/转向等模拟信号、固件在真实ECU上运行并输出执行器命令、脚本验证命令是否符合预期、结果回传CI。因为HIL动辄数小时,它被设计成只在release分支这类关键节点触发。

多重门禁的协同

如果把一条完整的嵌入式CI流水线想象成一栋楼的验收流程:

CI阶段建筑隐喻
Build(编译)检查材料是否齐全,图纸是否能实现
Static Analysis(静态分析)结构计算审查:力学上是否安全
Unit Tests(单元测试)每个预制构件的单独承重测试
Coverage(覆盖率)检查是否每一根钢筋都被测试过
Traceability(追溯)验收清单:是否每一项设计要求都被验证
HIL Test(硬件在环)整体建筑的风洞实验/振动台测试

核心洞察:每道CI门禁对应建筑验收的一道工序,缺一道就缺一层保障。 编译查材料是否齐全、静态分析做结构计算审查、单元测试是每个预制构件的承重测试、覆盖率检查是否每根钢筋都被检验过、追溯是验收清单、HIL是整体建筑的风洞实验。看懂这张对应表,你就明白一条“只跑编译“的流水线,相当于一栋只有图纸、没有验收的楼。

CI的反模式:你应该避免的五个陷阱

在实践中,CI流水线很容易从“高效保障“退化成“形式主义负担“。以下是在汽车嵌入式项目中常见的五个CI反模式:

反模式1:流水线太长,反馈太慢

如果git push之后要等三个小时才能看到CI结果,开发者会在推送后去开会、去吃午饭、去写下一段代码。当他们回来发现CI是红的时,他们已经脱离了上下文,定位和修复的效率大打折扣。

解决原则: 流水线的总运行时间不应超过15分钟。如果超过,考虑分层策略:快速检查(编译+静态分析+单元测试,5分钟内)在每次push时触发,慢速检查(覆盖率+追溯+安全扫描)在PR合并前触发,HIL测试在夜间批量运行。

反模式2:CI失败不被当作紧急事件

你见过多少次这样的场景:CI已经红了三天,但团队继续在上面积累更多提交,因为“那个失败是已知问题,我们会修的“。每次CI失败而团队选择忽略,CI作为门禁机制的威慑力就减弱一分。

解决原则: 团队合约:CI红了,当前所有新功能开发暂停,优先级最高的任务是修CI。要么修代码,要么修测试,要么增补抑制(附带偏离记录),但不可以忽视。这需要团队文化的强力支持,不能只靠工具规则。

反模式3:流水线阶段的职责不清

一个流水线阶段做了太多事情,或者一个步骤的名字模糊(“Quality Check“到底是什么检查?),导致失败后需要花时间排查是哪个环节出了问题。

解决原则: 每个流水线阶段做一件事,用清晰的名字。eng-lite的五阶段(Build / Static Analysis / Unit Tests / Coverage / Traceability)是很好的起点。失败信息要包含足够的上下文,让开发者不需要SSH到CI容器里调试。

反模式4:没有缓存机制

每次都从零开始安装依赖(sudo apt-get install)会浪费大量时间。生产项目的CI应该使用预构建的Docker镜像,依赖在镜像构建阶段就安装好了,每个CI run只需要docker pull。

反模式5:CI配置本身没有版本控制和质量审查

CI配置文件(YAML、Jenkinsfile)是代码的一种形式。它应该有代码审查、应该有变更历史、应该遵循DRY原则。见过一些项目,Jenkins的构建脚本是在Jenkins Web UI里点出来的:没有版本控制、没有审查记录、除了构建的管理员之外没有人能重现构建。

核心洞察:CI的真正价值是把工程纪律从人类的自我约束变成系统的不可绕过性。 人会在赶进度时跳过静态分析、在凌晨三点觉得改动太小而不跑测试;CI容器却不知道时间、不知SOP节点,会一次不漏地执行你配置的每条检查。但流水线同样会退化为形式主义——超过15分钟、红了三天没人修、阶段职责不清、无缓存、配置不进版本库,这五个反模式是它在生产中的常见死法,也是团队文化溃堤的信号。

数一数你的流水线有几道门

打开你当前工作的代码仓库。找到CI配置文件:Jenkinsfile、.gitlab-ci.yml、.github/workflows/*.yml。数一数这条流水线有多少个检查阶段。然后问自己: 如果把CI流水线当成一个独立招聘的市场:有人只跑“编译通过“,有人跑“编译+测试+覆盖率“,还有人跑“编译+测试+覆盖率+静态分析+追溯+安全扫描“。你的流水线在哪个职级?它能保护你的代码到哪一层? 从今天开始,你可以做的第一步:给你的项目增加一个最小的CI门禁,哪怕只有编译检查。一个“至少我每次push时能确认代码没有语法错误“的CI,比“我打算以后做一次完整的CI但一直没空做“要强一百倍。工程化的每一步都不需要完美,但需要开始。开始比完美重要一万倍。


本篇小结

  • CI流水线配置:本章带你完整走过了eng-lite的CI流水线配置。
  • YAML结构:你看到了GitHub Actions的YAML结构。
  • 检查阶段职责:理解了五个检查阶段的各自职责。
  • HIL测试挂接:看到了HIL测试如何挂接到这条流水线上。
  • 工程纪律执行器:CI流水线本质上是一个“自动化工程纪律执行器“。它不创造新的价值,但它确保已有的质量管理流程被忠实地执行,在每一次push之后,无一例外。这种可靠性,是手工流程永远无法达到的。

【下集预告】:最后一章,第5.6章,我们将走进需求追溯的世界,也是整个工程基础设施体系的最后一环。你会看到trace.py如何扫描源代码中的@req标注,如何生成追溯矩阵,如何在CI门禁中确保“没有不受测试保护的需求“。这是竣工验收的最后一步:只有当每一条安全需求都能被追溯到至少一个测试用例时,这栋楼才算真正盖好了。