Files
ViewDesignEngine/docs/guides/testing.md
T

457 lines
11 KiB
Markdown
Raw Normal View History

# 测试指南
ViewDesignEngine 使用 Google Test 框架,通过 CTest 运行测试。
## 目录
1. [测试架构](#1-测试架构)
2. [运行测试](#2-运行测试)
3. [编写测试](#3-编写测试)
4. [测试规范](#4-测试规范)
5. [Sanitizer 测试](#5-sanitizer-测试)
6. [性能基准](#6-性能基准)
7. [模糊测试](#7-模糊测试)
8. [CI 集成](#8-ci-集成)
---
## 1. 测试架构
```
tests/
├── CMakeLists.txt # 顶层测试 CMake
├── foundation/ # 基础设施测试
│ ├── CMakeLists.txt
│ └── test_io_obj.cpp
├── core/ # 核心模块测试
│ ├── CMakeLists.txt
│ ├── test_polygon.cpp
│ ├── test_distance.cpp
│ └── test_cam_5axis.cpp
├── curves/ # 曲线曲面测试
├── mesh/ # 网格处理测试
│ ├── CMakeLists.txt
│ ├── test_fea_mesh.cpp
│ ├── test_reverse_engineering.cpp
│ └── test_xxx.cpp
├── spatial/ # 空间索引测试
├── boolean/ # 布尔运算测试
├── collision/ # 碰撞检测测试
├── brep/ # B-Rep 测试
├── sdf/ # SDF 测试
├── sketch/ # 草图约束测试
├── gpu/ # GPU 测试
└── fuzz/ # 模糊测试
```
### 测试数量
| 模块 | 测试数 | 状态 |
|------|--------|------|
| foundation | ~30 | ✅ |
| core | ~40 | ✅ |
| curves | ~50 | ✅ |
| mesh | ~60 | ✅ |
| spatial | ~30 | ✅ |
| boolean | ~40 | ✅ |
| collision | ~30 | ✅ |
| brep | ~70 | ✅ |
| sdf | 189 | ✅ |
| sketch | ~10 | ✅ |
| gpu | ~5 | ✅ |
| **合计** | **~500+** | ✅ |
## 2. 运行测试
### 基础命令
```bash
# 构建并运行所有测试
cmake -B build -DBUILD_TESTS=ON
cmake --build build -j$(nproc)
cd build && ctest --output-on-failure
# 并行运行
ctest -j$(nproc) --output-on-failure
# 详细输出
ctest -V
# 运行特定测试
ctest -R test_brep # 名称匹配
ctest -R "test_brep|test_sdf" # 多个模式
# 排除特定测试
ctest -E test_fuzz
```
### 直接运行可执行文件
```bash
# 运行单个测试文件
./build/tests/brep/test_brep
# 运行特定测试用例
./build/tests/brep/test_brep --gtest_filter="*Box*"
# 列出所有测试用例
./build/tests/brep/test_brep --gtest_list_tests
# 重复运行(检测不稳定性)
./build/tests/mesh/test_fea_mesh --gtest_repeat=100
```
### Docker 中运行
```bash
docker run --rm --cpus=4 --memory=8g \
-v $(pwd):/ws vde-builder bash -c \
"cd /ws/build && ctest -j4 --output-on-failure"
```
## 3. 编写测试
### 3.0 如何添加新测试(完整步骤)
向 ViewDesignEngine 添加新测试遵循以下标准工作流:
**步骤 1: 创建测试文件**
在对应模块的测试目录下创建 `test_<feature>.cpp`
```bash
# 例如:为 brep 模块的新功能添加测试
touch tests/brep/test_new_feature.cpp
```
**步骤 2: 编写测试代码**
```cpp
// tests/brep/test_new_feature.cpp
#include <gtest/gtest.h>
#include "vde/brep/modeling.h"
#include "vde/brep/new_feature.h" // 被测头文件
using namespace vde::brep;
using namespace vde::core;
// 基本功能测试
TEST(NewFeatureTest, BasicOperation) {
auto box = make_box(2, 2, 2);
auto result = new_feature_operation(box, /* params */);
EXPECT_TRUE(result.is_valid());
}
// 边界条件测试
TEST(NewFeatureTest, EmptyInput) {
BrepModel empty;
EXPECT_THROW(new_feature_operation(empty), std::invalid_argument);
}
// 数值精度测试
TEST(NewFeatureTest, NumericalPrecision) {
auto sphere = make_sphere(1.0);
auto result = new_feature_operation(sphere, /* params */);
EXPECT_NEAR(result.volume(), 4.18879, 1e-5);
}
```
**步骤 3: 注册到 CMake**
编辑 `tests/<module>/CMakeLists.txt`,使用标准宏:
```cmake
# tests/brep/CMakeLists.txt
# 方式 A:使用 add_vde_test 宏(自动链接聚合库 vde)
add_vde_test(test_new_feature)
# 方式 B:如需精确控制链接(避免聚合库中其他模块的预存错误)
add_executable(test_new_feature test_new_feature.cpp)
target_link_libraries(test_new_feature PRIVATE
vde_brep vde_curves vde_core vde_foundation
GTest::gtest GTest::gtest_main
)
add_test(NAME test_new_feature COMMAND test_new_feature)
```
**步骤 4: 本地验证**
```bash
# 配置构建
cmake -B build -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release
# 只编译新测试目标
cmake --build build --target test_new_feature -j$(nproc)
# 运行新测试
./build/tests/brep/test_new_feature
# 运行特定用例
./build/tests/brep/test_new_feature --gtest_filter="*BasicOperation*"
# 确认不破坏已有测试
cd build && ctest --output-on-failure -j$(nproc)
```
**步骤 5: Docker 环境验证(推荐)**
```bash
docker run --rm --cpus=2 --memory=8g \
-v /root/workspace:/ws -w /ws/ViewDesignEngine \
vde-builder:latest bash -c "
cmake -B build_verify -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release &&
cmake --build build_verify --target test_new_feature -j2 &&
./build_verify/tests/brep/test_new_feature
"
```
### 3.1 基本模板
```cpp
// tests/mesh/test_my_feature.cpp
#include <gtest/gtest.h>
#include <vde/mesh/halfedge_mesh.h>
#include <vde/core/point.h>
using namespace vde::core;
using namespace vde::mesh;
// 基本断言
TEST(MyFeatureTest, CreateEmptyMesh) {
HalfedgeMesh mesh;
EXPECT_EQ(mesh.vertex_count(), 0);
EXPECT_EQ(mesh.face_count(), 0);
}
// 带 fixture 的测试
class MyFeatureFixture : public ::testing::Test {
protected:
void SetUp() override {
mesh.add_vertex(Point3D(0, 0, 0));
mesh.add_vertex(Point3D(1, 0, 0));
mesh.add_vertex(Point3D(0, 1, 0));
mesh.add_face({0, 1, 2});
}
HalfedgeMesh mesh;
};
TEST_F(MyFeatureFixture, FaceCount) {
EXPECT_EQ(mesh.face_count(), 1);
}
TEST_F(MyFeatureFixture, VertexPositions) {
EXPECT_NEAR(mesh.vertex(0).x, 0.0, 1e-6);
EXPECT_NEAR(mesh.vertex(1).x, 1.0, 1e-6);
}
```
### 3.2 参数化测试
```cpp
// 使用不同参数重复测试
class BooleanOpTest : public ::testing::TestWithParam<
std::tuple<std::string, int, int>> {};
TEST_P(BooleanOpTest, ConsistentResult) {
auto [op_name, n1, n2] = GetParam();
// ... test logic using parameters ...
}
INSTANTIATE_TEST_SUITE_P(
MeshBoolean,
BooleanOpTest,
::testing::Values(
std::make_tuple("union", 100, 50),
std::make_tuple("intersection", 200, 100),
std::make_tuple("difference", 300, 150)
));
```
### 3.3 注册到 CMake
```cmake
# tests/mesh/CMakeLists.txt
add_executable(test_my_feature test_my_feature.cpp)
target_link_libraries(test_my_feature
PRIVATE vde_mesh vde_core
PRIVATE GTest::gtest GTest::gtest_main
)
add_test(NAME test_my_feature COMMAND test_my_feature)
```
## 4. 测试规范
### 命名规范
```
测试文件名: test_<feature>.cpp
测试套件名: <Feature>Test 或 <Module>Test
测试用例名: <Action><Condition> 或 <MethodName><Scenario>
示例:
- TEST(BrepValidateTest, ClosedShellPassesValidation)
- TEST(MeshSimplifyTest, SimplifyingCubeReducesFaceCount)
```
### 断言选择
| 场景 | 推荐断言 |
|------|---------|
| 相等比较 | `EXPECT_EQ(a, b)` |
| 浮点比较 | `EXPECT_NEAR(a, b, tolerance)` |
| 布尔检查 | `EXPECT_TRUE(condition)` |
| 非致命错误 | `EXPECT_*` 继续执行 |
| 致命错误 | `ASSERT_*` 立即终止 |
| 异常检查 | `EXPECT_THROW(expr, exception_type)` |
### 测试清单
每个功能模块的测试应覆盖:
- [ ] **正常路径** (Happy Path)
- [ ] **边界条件** (空输入、极值、零值)
- [ ] **错误处理** (无效输入、超范围)
- [ ] **退化情况** (共线/共面点、零面积面)
- [ ] **数值稳定性** (接近容差的场景)
- [ ] **性能回归** (大规模输入不超时)
### 反模式
```cpp
// ❌ 测试依赖全局状态
static int counter = 0;
TEST(Foo, Bar) { EXPECT_EQ(counter++, 0); } // 不可重复
// ❌ 测试依赖执行顺序
TEST(Foo, A) { /* 设置全局状态 */ }
TEST(Foo, B) { /* 依赖 A 的状态 */ }
// ❌ 过于宽松的容差
EXPECT_NEAR(result, expected, 100.0); // 容差过大
// ❌ 没有断言(不验证任何东西)
TEST(Foo, Bar) {
auto x = compute();
// 没有 EXPECT/ASSERT
}
```
## 5. Sanitizer 测试
### Address Sanitizer (ASan)
```bash
# 构建 ASan 版本
cmake -B build_asan \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_SANITIZERS=ON \
-DVDE_USE_OPENMP=OFF
cmake --build build_asan -j$(nproc)
# 运行测试
cd build_asan
ASAN_OPTIONS=detect_leaks=1:abort_on_error=1 \
ctest --output-on-failure -j4
```
### Undefined Behavior Sanitizer (UBSan)
```bash
# 手动添加 UBSan 标志
cmake -B build_ubsan \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="-fsanitize=undefined"
cmake --build build_ubsan -j$(nproc)
cd build_ubsan && ctest --output-on-failure
```
### Thread Sanitizer (TSan)
```bash
cmake -B build_tsan \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="-fsanitize=thread" \
-DVDE_USE_OPENMP=OFF
cmake --build build_tsan -j$(nproc)
cd build_tsan && ctest --output-on-failure
```
## 6. 性能基准
### 运行基准测试
```bash
# 构建基准测试
cmake -B build_bench -DBUILD_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build_bench -j$(nproc)
# 运行所有基准
cd build_bench && ctest -R bench_
# 直接运行并查看详细报告
./build_bench/bench/bench_bvh --benchmark_format=json
```
### 基准模块
| 基准 | 内容 |
|------|------|
| `bench_bvh` | SAH 构建 (N=1K/10K/100K) + 射线查询 |
| `bench_delaunay` | 2D Bowyer-Watson (N=100/1K/10K) |
| `bench_boolean` | 3D 网格布尔 (N=100/500) |
| `bench_curves` | Bezier/BSpline evaluate 吞吐量 |
| `bench_spatial` | R-Tree/KD-Tree 构建 + kNN |
## 7. 模糊测试
```bash
# 构建并运行模糊测试
cmake -B build -DBUILD_TESTS=ON
cmake --build build -j$(nproc)
# 运行模糊测试
./build/tests/fuzz/test_fuzz_sdf
./build/tests/fuzz/test_fuzz_boolean
./build/tests/fuzz/test_fuzz_format
```
模糊测试覆盖的模块:
- **SDF**: 随机 CSG 树组合,检测 NaN/崩溃
- **Boolean**: 随机网格布尔运算,检测拓扑错误
- **Format**: 随机几何 → 导出 → 导入 → 验证一致性
## 8. CI 集成
VDE 使用 Gitea CI 自动运行测试。
### CI 配置文件
`.gitea/workflows/build.yml`:
```yaml
name: Build & Test
on: [push, pull_request]
jobs:
build-and-test:
runs-on: ubuntu-latest
container: vde-builder:latest
steps:
- name: Build
run: |
cmake -B build_ci -DCMAKE_BUILD_TYPE=Release
cmake --build build_ci -j2
- name: Test
run: |
cd build_ci && ctest --output-on-failure -j2
```
### CI 检查项
- [ ] 编译通过 (零错误零警告)
- [ ] 所有测试通过 (ctest 返回 0)
- [ ] ASan 检查无内存错误
- [ ] 无新增编译警告