362 lines
8.4 KiB
Markdown
362 lines
8.4 KiB
Markdown
|
|
# 测试指南
|
||
|
|
|
||
|
|
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.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 检查无内存错误
|
||
|
|
- [ ] 无新增编译警告
|