6、测试预设(testPresets):定义测试配置、过滤、并行度等。

说实话,很多人把CMake Presets只当成编译配置的快捷方式。其实测试预设(testPresets)才是真正能提升团队效率的利器。我个人的经验是,一个项目能不能快速迭代,很大程度上取决于测试跑得够不够快、够不够准。

测试预设,说白了就是帮你把CTest的各种参数固化下来。你不用每次都在命令行里敲一堆-j-R-E之类的参数。你想想看,团队里新来的同事,只要一个cmake --preset test就能跑对测试,多省心。

6.1 测试预设的基本结构

测试预设和构建预设长得有点像,但它的核心是控制CTest的行为。我们先看一个最简单的例子:

{
  "version": 6,
  "testPresets": [
    {
      "name": "default",
      "displayName": "默认测试",
      "description": "跑所有单元测试,4核并行",
      "configurePreset": "default",
      "output": {
        "outputOnFailure": true,
        "verbosity": "default"
      },
      "execution": {
        "jobs": 4,
        "stopOnFailure": false
      }
    }
  ]
}

这里有个关键点:configurePreset字段。它告诉CMake,这个测试预设是基于哪个构建预设的。我在项目中遇到过有人忘了配这个字段,结果测试跑的是旧版本的二进制文件,排查了半天才发现问题。嗯,这个坑大家要注意。

6.2 过滤测试:只跑你关心的

大型项目里测试用例动辄上千个,全跑一遍太费时间。测试预设提供了强大的过滤机制。我个人习惯把测试分成几个层级:单元测试、集成测试、冒烟测试。每个层级对应一个预设。

{
  "testPresets": [
    {
      "name": "unit",
      "displayName": "单元测试",
      "configurePreset": "default",
      "filter": {
        "include": {
          "name": "^test_unit_"
        },
        "exclude": {
          "name": "slow"
        }
      }
    },
    {
      "name": "smoke",
      "displayName": "冒烟测试",
      "configurePreset": "default",
      "filter": {
        "include": {
          "name": "smoke"
        }
      },
      "execution": {
        "jobs": 1,
        "timeout": 300
      }
    }
  ]
}

你看,filter里的includeexclude支持正则表达式。我曾经有个项目,测试命名不规范,有的叫test_xxx,有的叫xxx_test。后来我统一了命名规范,再用正则过滤就方便多了。

小技巧: 过滤条件可以叠加。比如你想跑所有单元测试,但排除那些标记为"slow"的,就可以像上面那样写。CTest会先执行include匹配,再执行exclude剔除。

6.3 并行度与资源控制

并行度是测试预设里最实用的功能之一。你想想看,一个测试跑10分钟,如果单核跑,那得等到猴年马月。但并行度也不是越高越好,得看你的机器有多少核,以及测试之间有没有资源冲突。

{
  "testPresets": [
    {
      "name": "parallel",
      "displayName": "并行测试",
      "configurePreset": "default",
      "execution": {
        "jobs": 8,
        "resourceSpecFile": "${sourceDir}/resources.json",
        "testLoad": 4.0
      }
    }
  ]
}

这里有个testLoad参数,它控制的是CPU负载阈值。当系统负载超过这个值时,CTest会暂停启动新的测试。我在服务器上跑CI时,一般设成4.0,防止测试把CPU占满导致其他服务卡顿。

resourceSpecFile是CMake Presets 6引入的新功能。你可以为每个测试指定它需要的资源(比如GPU、内存大小)。CTest会根据资源分配情况智能调度测试。举个例子:

{
  "version": 6,
  "resources": {
    "gpu": {
      "type": "GPU",
      "count": 2
    }
  },
  "tests": [
    {
      "name": "test_gpu_1",
      "resources": {
        "gpu": 1
      }
    },
    {
      "name": "test_gpu_2",
      "resources": {
        "gpu": 1
      }
    }
  ]
}

这样,即使你设了jobs: 8,CTest也不会同时跑两个需要GPU的测试,因为GPU资源只有2个。嗯,这个设计很贴心。

6.4 环境变量与输出控制

测试环境往往需要一些特殊的环境变量。比如数据库连接串、API密钥之类的。测试预设可以帮你管理这些:

{
  "testPresets": [
    {
      "name": "integration",
      "displayName": "集成测试",
      "configurePreset": "default",
      "environment": {
        "DB_HOST": "localhost",
        "DB_PORT": "5432",
        "LOG_LEVEL": "debug"
      },
      "output": {
        "outputOnFailure": true,
        "maxFailures": 10,
        "shortProgress": true
      }
    }
  ]
}

输出控制这块,outputOnFailure是我最常用的。只输出失败的测试日志,成功的一概不显示。这样CI日志看起来清爽多了。maxFailures也很实用,一旦失败数达到阈值,CTest就停止跑剩下的测试,节省时间。

注意: 环境变量里不要硬编码敏感信息,比如密码。建议用${sourceDir}/.env之类的文件加载,或者用CI平台的密钥管理功能。

6.5 测试预设的继承与覆盖

和构建预设一样,测试预设也支持继承。你可以定义一个基础预设,然后派生出不同场景的预设:

{
  "testPresets": [
    {
      "name": "base",
      "displayName": "基础测试配置",
      "configurePreset": "default",
      "execution": {
        "jobs": 4,
        "stopOnFailure": false
      },
      "output": {
        "outputOnFailure": true
      }
    },
    {
      "name": "ci",
      "displayName": "CI环境测试",
      "inherits": "base",
      "execution": {
        "jobs": 8,
        "stopOnFailure": true
      },
      "filter": {
        "exclude": {
          "label": "flaky"
        }
      }
    },
    {
      "name": "dev",
      "displayName": "开发环境测试",
      "inherits": "base",
      "filter": {
        "include": {
          "name": "quick"
        }
      }
    }
  ]
}

你看,ci预设继承了base,但把并行度从4改成了8,并且开启了失败即停止。而dev预设只跑名字带"quick"的测试。这样团队里不同角色的人,都能用最适合自己的配置。

我曾经在团队里推广这个模式,一开始有人觉得麻烦。后来我给他们演示了一下:开发时用dev预设,几秒钟就跑完;提交前用ci预设,确保所有关键测试通过。大家用了都说好。

6.6 知识体系图

下面这张图总结了测试预设的核心逻辑:

testPresets configurePreset environment output 控制 execution.jobs filter 过滤 inherits 继承 本地开发 CI/CD 流水线 集成测试 测试预设的核心:配置 + 执行 + 过滤,适配不同场景

6.7 实战建议

最后,我分享几个实战中的小建议:

  • 命名规范:测试预设的名字最好和构建预设对应起来,比如debug-testrelease-test。这样一看就知道是哪个构建环境下的测试。
  • 不要滥用继承:继承层级太深会让人晕头转向。我一般控制在两层以内:一个基础预设,然后派生几个具体场景的预设。
  • 善用标签过滤:在测试代码里给测试打上标签(label),比如unitintegrationslow。然后在预设里用filter.label来筛选,比用正则匹配名字更灵活。
  • 本地测试和CI测试分开:本地测试追求速度,CI测试追求覆盖率。用不同的预设来管理,互不干扰。

核心要点: 测试预设不是简单的CTest参数封装,它是团队测试策略的体现。好的测试预设配置,能让每个人在合适的场景下跑合适的测试,既快又准。

好了,关于测试预设就聊到这里。你可以在自己的项目里试试看,从最简单的并行度配置开始,慢慢加上过滤、环境变量、继承。相信我,用上之后你就回不去了。


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