持续集成中的单元测试:配置GitHub Actions或Jenkins,每次提交自动运行测试

说实话,很多团队写单元测试挺认真的,但测试跑不跑、什么时候跑,完全靠自觉。我见过不少项目,测试写了一大堆,结果没人执行,最后成了摆设。这其实挺可惜的。

持续集成(CI)就是来解决这个问题的。它的核心思想很简单:每次代码提交,自动拉取代码、编译、运行测试,然后告诉你结果。说白了,就是把「人肉检查」变成「机器自动检查」。

我个人习惯,项目一开始就要配好CI。哪怕只有一条测试,也要让它自动跑起来。为什么?因为一旦养成习惯,后面加测试就是顺理成章的事。

为什么需要持续集成?

你想想看,一个团队三五个人,每个人都在本地跑测试。张三忘了拉最新代码,李四的测试环境跟别人不一样,王五干脆就没跑测试直接push了。结果呢?合并的时候各种冲突,编译不过,测试全红。

持续集成能帮你解决三个核心问题:

  • 一致性:每次都在同一个干净环境里跑测试,不会出现「我本地能过啊」的情况
  • 及时性:提交代码后几分钟内就能知道有没有破坏已有功能
  • 自动化:不需要任何人记住「提交前要跑测试」,机器替你做了

核心原则:如果测试没通过,代码就不应该合并到主分支。这是CI的底线,也是团队协作的基石。

GitHub Actions 配置实战

GitHub Actions 是目前最流行的CI工具之一。它跟GitHub仓库深度集成,配置起来非常方便。我个人最喜欢的一点是:不需要额外搭建服务器,GitHub帮你托管运行环境。

下面是一个典型的C++项目CI配置。我在项目中用过很多次,基本能覆盖大部分场景。

# .github/workflows/ci.yml
name: C++ CI

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

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Install dependencies
      run: |
        sudo apt-get update
        sudo apt-get install -y cmake g++ libgtest-dev

    - name: Configure CMake
      run: cmake -B build -DCMAKE_BUILD_TYPE=Debug

    - name: Build
      run: cmake --build build --parallel

    - name: Run tests
      run: cd build && ctest --output-on-failure

这个配置做了几件事:

  • 监听 main 和 develop 分支的 push 事件,以及针对 main 的 pull request
  • 在 Ubuntu 最新版上运行
  • 安装 cmake、g++ 和 Google Test 库
  • 用 CMake 配置、编译项目
  • 最后用 ctest 运行所有测试,失败时输出详细信息

小技巧:ctest 的 --output-on-failure 参数非常有用。没有它,测试失败了你只能看到一个「FAILED」,完全不知道哪里出了问题。加上这个参数,失败测试的控制台输出会直接显示在CI日志里。

Jenkins 配置实战

Jenkins 是老牌CI工具,灵活性极高。如果你的项目有特殊需求,比如需要特定的硬件环境、需要跟内部系统集成,Jenkins 往往是更好的选择。

Jenkins 的配置主要通过 Jenkinsfile 来实现。下面是一个典型的流水线配置:

// Jenkinsfile
pipeline {
    agent any

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Install Dependencies') {
            steps {
                sh 'apt-get update && apt-get install -y cmake g++ libgtest-dev'
            }
        }

        stage('Configure') {
            steps {
                sh 'cmake -B build -DCMAKE_BUILD_TYPE=Debug'
            }
        }

        stage('Build') {
            steps {
                sh 'cmake --build build --parallel'
            }
        }

        stage('Test') {
            steps {
                sh 'cd build && ctest --output-on-failure'
            }
        }
    }

    post {
        always {
            junit 'build/test-results/*.xml'
        }
    }
}

Jenkins 的流水线语法稍微啰嗦一点,但好处是每个阶段都可以独立配置。比如你可以给「Test」阶段设置超时时间,或者只在特定分支上运行某些阶段。

注意:Jenkins 默认不会清理工作空间。如果你不手动清理,上次构建的残留文件可能会影响本次构建。我建议在流水线开头加一个 cleanWs() 步骤,或者用 Docker 容器来保证环境纯净。

测试结果的可视化

跑完测试只是第一步。怎么让团队快速看到测试结果?我个人习惯用测试报告插件。

GitHub Actions 原生支持测试结果展示。你只需要在配置里加上:

- name: Test Report
  uses: dorny/test-reporter@v1
  if: success() || failure()
  with:
    name: C++ Tests
    path: build/test-results/*.xml
    reporter: java-junit

Jenkins 的话,用 JUnit 插件就行。上面的 Jenkinsfile 里已经加了 junit 'build/test-results/*.xml',它会自动解析测试结果并生成趋势图。

为什么要做可视化?因为趋势比单次结果更重要。你可以看到测试数量是增加还是减少,失败率是上升还是下降。我曾经在一个项目里,通过测试趋势图发现某个模块的测试一直在缓慢减少——原来是有人偷偷删测试,没人发现。

常见问题与避坑指南

配置CI的时候,有几个坑我踩过,分享给你:

  • 环境不一致:本地用 macOS,CI 用 Ubuntu,结果链接器行为不一样。解决方案是用 Docker 统一环境。
  • 测试太慢:一开始所有测试都跑,后来项目大了,一次CI要跑40分钟。我后来把测试分成了「快速测试」和「完整测试」,提交时只跑快速测试,合并前再跑完整测试。
  • 忽略测试失败:有人觉得「测试失败没关系,先合并再说」。这其实是最危险的。一旦开了这个口子,测试就失去了意义。

我的建议:在分支保护规则里,把CI通过设为合并的前置条件。这样任何人都无法绕过测试直接合并代码。

知识体系总览

下面这张图展示了持续集成中单元测试的核心流程和关键环节:

持续集成中的单元测试流程 开发者提交代码 CI 自动触发 拉取代码 安装依赖 编译项目 运行单元测试 测试通过 → 允许合并 | 测试失败 → 阻止合并

这张图展示了从开发者提交代码到CI给出结果的完整链路。每个环节都很重要,但最关键的还是最后一步:测试结果必须影响合并决策。否则前面的工作都白做了。

总结

持续集成不是锦上添花,而是团队协作的必需品。它把「人治」变成了「法治」,让测试真正发挥作用。

配置CI其实不难,难的是坚持。我见过太多团队,CI配好了,但没人看结果,测试红了也照样合并。如果你真的想让单元测试发挥作用,就从今天开始,把CI跑起来,并且严格执行「测试不通过不合并」的纪律。

嗯,就这些。希望你的项目也能从CI中受益。


公众号:蓝海资料掘金营,微信deep3321