Bazel C++ 构建系列文档(八):测试与持续集成
1. Bazel 测试系统概览
Bazel 内置了强大的测试系统,支持多种测试类型和运行策略。
1.1 测试类型
|
|
|
|
|---|---|---|
|
|
cc_test |
|
|
|
cc_test
args=["--benchmark"] |
|
|
|
cc_test
args |
|
|
|
cc_test
timeout |
|
|
|
cc_test
data |
|
1.2 测试执行流程
测试执行流程:1. 测试发现 → bazel test //...2. 测试编译 → 编译测试目标3. 测试运行 → 在沙箱中执行测试4. 结果收集 → 收集输出和退出码5. 结果分析 → 判断通过/失败
2. 单元测试基础
2.1 基本 cc_test 规则
# tests/utils_test.cc#include "gtest/gtest.h"#include "utils.h"TEST(UtilsTest, SplitString) {std::vector<std::string> result = utils::Split("a,b,c", ',');EXPECT_EQ(result.size(), 3);EXPECT_EQ(result[0], "a");EXPECT_EQ(result[1], "b");EXPECT_EQ(result[2], "c");}TEST(UtilsTest, JoinString) {std::vector<std::string> parts = {"hello", "world"};std::string result = utils::Join(parts, " ");EXPECT_EQ(result, "hello world");}int main(int argc, char** argv) {::testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();}
# tests/BUILDcc_test(name = "utils_test",srcs = ["utils_test.cc"],deps = ["//src:utils","@com_google_googletest//:gtest_main",],)
2.2 测试依赖管理
# 使用别名简化测试依赖cc_test(name = "complex_test",srcs = ["complex_test.cc"],deps = [":complex_lib","@com_google_googletest//:gtest","@com_google_googletest//:gtest_main","@com_google_benchmark//:benchmark_main",],)
2.3 测试配置
# .bazelrctest --test_output=errors # 只显示错误输出test --test_filter="*Test" # 只运行 *Test 测试test --test_timeout=300,60,60 # 总/每个测试/每个动作超时test --test_summary=compact # 简洁的测试摘要test --test_results_dir=test_logs # 测试结果目录
3. Google Test 集成
3.1 Google Test 基本用法
// tests/example_test.cc#include"gtest/gtest.h"#include"example.h"// 正常测试TEST(ExampleTest, AddNumbers) {EXPECT_EQ(example::Add(2, 3), 5);EXPECT_EQ(example::Add(-1, 5), 4);}// 参数化测试class ExampleParamTest : public ::testing::TestWithParam<int> {protected:void SetUp() override {// 每个参数的设置}};TEST_P(ExampleParamTest, MultiplyByTwo) {int input = GetParam();EXPECT_EQ(example::Multiply(input, 2), input * 2);}// 测试用例INSTANTIATE_TEST_SUITE_P(AllValues, ExampleParamTest,testing::Values(0, 1, 2, 3, 4));
3.2 Google Mock 使用
// tests/mock_example_test.cc#include"gmock/gmock.h"#include"example.h"// 模拟类class MockExample : public example::ExampleInterface {public:MOCK_METHOD(int, Add, (int a, int b), (override));};// 测试模拟类TEST(MockExampleTest, AddBehavior) {MockExample mock;EXPECT_CALL(mock, Add(2, 3)).WillOnce(testing::Return(5));EXPECT_CALL(mock, Add(-1, 5)).WillOnce(testing::Return(4));// 调用模拟方法EXPECT_EQ(mock.Add(2, 3), 5);EXPECT_EQ(mock.Add(-1, 5), 4);}
3.3 自定义测试夹具
// tests/fixture_test.cc#include"gtest/gtest.h"#include"database.h"class DatabaseTest : public ::testing::Test {protected:void SetUp() override {// 测试前的准备工作db_ = std::make_unique<Database>("test.db");db_->CreateTables();}void TearDown() override {// 测试后的清理工作if (db_) {db_->Close();}}std::unique_ptr<Database> db_;};TEST_F(DatabaseTest, InsertAndSelect) {db_->InsertUser(1, "Alice");auto user = db->SelectUser(1);ASSERT_NE(user, nullptr);EXPECT_EQ(user->name(), "Alice");}// 参数化夹具class ParamDatabaseTest : public ::testing::TestWithParam<std::string> {protected:void SetUp() override {db_name_ = GetParam();db_ = std::make_unique<Database>(db_name_);}std::string db_name_;std::unique_ptr<Database> db_;};TEST_P(ParamDatabaseTest, TestAllImplementations) {// 测试不同实现的数据库}
4. 测试运行控制
4.1 测试过滤
# 运行所有测试bazel test //...# 运行指定包的测试bazel test //tests:*# 运行特定测试bazel test //tests:utils_test# 使用通配符过滤bazel test //tests:*_test# 使用 -test_filter 参数bazel test //... --test_filter="*Test*"# 更复杂的过滤bazel test //... --test_filter="- flaky_test" # 排除 flaky_test
4.2 测试标签
# tests/BUILDcc_test(name = "unit_test",srcs = ["unit_test.cc"],tags = ["unit"], # 单元测试标签)cc_test(name = "integration_test",srcs = ["integration_test.cc"],tags = ["integration", "slow"], # 集成测试、慢速测试)cc_test(name = "flaky_test",srcs = ["flaky_test.cc"],tags = ["flaky"], # 不稳定测试)
# 按标签运行测试bazel test //... --test_tags_filters=unit # 只运行单元测试bazel test //... --test_tags_filters=-slow # 排除慢速测试bazel test //... --test_tags_filters=unit,integration # 运行 unit 和 integration
4.3 并发测试控制
# .bazelrctest --test_timeout=300,60,60 # 总时间/每个测试/每个动作test --test_summary=terse # 简洁输出test --test_output=errors # 只显示错误test --local_test_jobs=8 # 本地并发数test --runs_per_test=3 # 每个测试运行次数# 测试大小分类test --test_size_filters=small # 只运行小测试test --test_size_filters=medium # 只运行中测试test --test_size_filters=large # 只运行大测试
5. 性能测试(Benchmark)
5.1 Google Benchmark 集成
// tests/benchmark_test.cc#include<benchmark/benchmark.h>#include"utils.h"static void BM_StringConstruction(benchmark::State& state) {for (auto _ : state) {std::string s(1024, 'x');benchmark::DoNotOptimize(s);}}BENCHMARK(BM_StringConstruction);static void BM_VectorAppend(benchmark::State& state) {for (auto _ : state) {std::vector<int> v;for (int i = 0; i < state.range(0); ++i) {v.push_back(i);}benchmark::DoNotOptimize(v);}}BENCHMARK(BM_VectorAppend)->Range(8, 8<<10);static void BM_SortVector(benchmark::State& state) {std::vector<int> data(10000);std::iota(data.begin(), data.end(), 0);std::random_shuffle(data.begin(), data.end());for (auto _ : state) {auto copy = data;std::sort(copy.begin(), copy.end());benchmark::DoNotOptimize(copy);}}BENCHMARK(BM_SortVector);
# tests/BUILDcc_test(name = "benchmark_test",srcs = ["benchmark_test.cc"],deps = ["//src:utils","@com_google_benchmark//:benchmark_main",],args = ["--benchmark_time_unit=ms"], # 参数化)
5.2 运行性能测试
# 运行所有性能测试bazel test //tests:benchmark_test# 显示详细输出bazel test //tests:benchmark_test --test_output=all# 只运行特定基准测试bazel test //tests:benchmark_test --test_filter="BM_SortVector"# 使用 Google Benchmark 的选项bazel test //tests:benchmark_test -- --benchmark_repetitions=10bazel test //tests:benchmark_test -- --benchmark_time_unit=us
6. 测试数据文件
6.1 使用 data 属性
# tests/data_test.cc#include "gtest/gtest.h"#include <fstream>class DataTest : public ::testing::Test {protected:void SetUp() override {// 测试数据文件在 test_data 目录data_file_ = testing::TempDir() + "/test_data.txt";std::ofstream f(data_file_);f << "test data";f.close();}std::string data_file_;};TEST_F(DataTest, ReadTestData) {std::ifstream f(data_file_);std::string content((std::istreambuf_iterator<char>(f)),std::istreambuf_iterator<char>());EXPECT_EQ(content, "test data");}
# tests/BUILDcc_test(name = "data_test",srcs = ["data_test.cc"],data = ["test_data/*.txt", # 测试数据文件"//data:test_config.json", # 其他包的数据文件],)
6.2 外部测试数据
# tests/BUILDcc_test(name = "external_data_test",srcs = ["external_data_test.cc"],data = ["@com_example_test_data//data:large_dataset.bin",],)# WORKSPACE 或 MODULE.bazelhttp_archive(name = "com_example_test_data",urls = ["https://example.com/test_data.zip"],strip_prefix = "test_data",build_file_content = """package(default_visibility = ["//visibility:public"])filegroup(name = "data", srcs glob(["**/*"]))""",)
7. 测试沙箱与隔离
7.1 测试沙箱特性
Bazel 使用沙箱运行测试,确保测试隔离性和可重现性:
-
每个测试在独立沙箱中运行 -
测试不能修改源文件或构建产物 -
测试只能访问 data属性指定的文件 -
环变量被限制在最小范围
7.2 自定义测试环境
# tests/BUILDcc_test(name = "env_test",srcs = ["env_test.cc"],env_vars = {"TEST_ENV_VAR": "value", # 设置环境变量"PATH": "/usr/bin:/bin", # 自定义 PATH},)
7.3 测试超时控制
# tests/BUILDcc_test(name = "fast_test",srcs = ["fast_test.cc"],timeout = 5, # 5 秒超时)cc_test(name = "slow_test",srcs = ["slow_test.cc"],timeout = 300, # 5 分钟超时size = "large", # 标记为大测试)
8. 测试结果分析
8.1 测试输出格式
# 详细的测试输出bazel test //... --test_output=all# 只失败的测试bazel test //... --test_output=errors# XML 格式输出bazel test //... --test_output=xml:test_results.xml# JSON 格式输出bazel test //... --test_output=json:test_results.json
8.2 测试结果目录
# 查看测试结果位置bazel info bazel-testlogs# 查看特定测试的日志cat bazel-testlogs/tests/utils_test/test.log# 查看失败的详细信息cat bazel-testlogs/tests/utils_test/test.failed
8.3 测试报告生成
# 测试报告脚本def generate_test_report():import subprocessimport json# 运行测试并收集结果result = subprocess.run(["bazel", "test", "//...", "--test_output=json"],capture_output=True,text=True)# 解析 JSON 结果results = json.loads(result.stdout)# 生成报告report = {"total": len(results["tests"]),"passed": sum(1 for t in results["tests"] if t["status"] == "PASSED"),"failed": sum(1 for t in results["tests"] if t["status"] == "FAILED"),"flaky": sum(1 for t in results["tests"] if t["status"] == "FLAKY"),}return report
9. 持续集成集成
9.1 GitHub Actions 示例
# .github/workflows/bazel-test.ymlname: Bazel Testson: [push, pull_request]jobs:test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v3- name: Install Bazeluses: bazelbuild/setup-bazelisk@v1- name: Cache Bazel outputuses: actions/cache@v2with:path: '~/.cache/bazel'key: {{ hashFiles('**/*.bzl', '**/BUILD*', '**/*.bazel') }}- name: Buildrun: bazel build //...- name: Run testsrun: bazel test //... --test_output=errors --local_test_jobs=8- name: Run benchmarksrun: bazel test //tests:benchmark_test -- --benchmark_repetitions=5- name: Upload test resultsuses: actions/upload-artifact@v2if: always()with:name: test-resultspath: bazel-testlogs/
9.2 CI 中的测试优化
# CI 中常用的测试命令bazel test //... --test_timeout=600,120,60 # 防止测试超时bazel test //... --test_summary=terse # 减少输出bazel test //... --runs_per_test=1 # CI 中只运行一次bazel test //... --flaky_test_attempts=3 # 重试失败测试# 只运行关键测试bazel test //tests:unit --test_size_filters=smallbazel test //tests:integration --test_size_filters=medium
9.3 测试监控
# 测试监控脚本import subprocessimport jsonimport sysdef monitor_tests():# 监控测试执行result = subprocess.run(["bazel", "test", "//...","--test_output=json","--local_test_jobs=4"], capture_output=True, text=True)# 分析测试结果data = json.loads(result.stdout)# 检查失败测试failed_tests = [t for t in data["tests"] if t["status"] == "FAILED"]if failed_tests:print(f"❌ {len(failed_tests)} tests failed:")for test in failed_tests:print(f" - {test['name']}")sys.exit(1)else:print(f"✅ All {len(data['tests'])} tests passed")
10. 测试最佳实践
10.1 测试组织原则
✅ 推荐做法 ❌ 避免───────────────────────── ─────────────────────────每个测试专注单一职责 在一个测试中测试多个功能使用有意义的测试名称 使用无意义的命名如 test1测试应该是独立的 测试之间有依赖测试应该快速运行 包含耗时操作如网络请求使用模拟对象 依赖外部服务测试覆盖率 >80% 不写关键测试的测试
10.2 测试命名约定
// 好的命名TEST(UtilsTest, SplitStringByComma) // 类名 + 功能描述TEST(ParserTest, InvalidInputReturnsError)TEST(DatabaseTest, InsertDuplicateUserReturnsError)// 避免的命名TEST(T1) // 无意义TEST(TestSomething) // 太泛TEST(utils) // 缺少功能描述
10.3 测试数据管理
tests/├── BUILD├── unit/ # 单元测试│ ├── BUILD│ └── ...├── integration/ # 集成测试│ ├── BUILD│ └── test_data/│ ├── valid_input.json│ └── error_cases.json├── performance/ # 性能测试│ ├── BUILD│ └── datasets/│ └── large_dataset.bin└── fixtures/ # 测试夹具└── mock_services.h
11. 小结
本篇详细介绍了 Bazel 的测试系统:
-
✅ cc_test规则和测试类型 -
✅ Google Test/Google Mock 集成 -
✅ 测试运行控制和过滤 -
✅ 性能测试(Benchmark) -
✅ 测试数据文件管理 -
✅ 测试沙箱与隔离 -
✅ 测试结果分析 -
✅ CI/CD 集成 -
✅ 测试最佳实践
夜雨聆风