从实验报告到真实BUG我用因果图法给‘三角形判断’程序挖出了隐藏缺陷附完整C/C代码复盘1. 黑盒测试的艺术当理论遇上实战在软件测试的世界里黑盒测试就像一场精心设计的侦探游戏。我们不需要知道代码内部的实现细节而是通过输入和输出的蛛丝马迹来验证程序是否按照预期工作。而因果图法就是这场游戏中最犀利的破案工具之一。最近我在一个看似简单的三角形判断程序中发现了一个教科书级别的隐藏缺陷。这个程序的功能是根据用户输入的三个边长判断它们能否构成三角形以及构成什么类型的三角形普通、等腰或等边。表面上看这个程序通过了常规测试但在使用因果图法系统分析后我发现了一个关键漏洞——当输入(200,200,1)时程序错误地将其判断为普通三角形而实际上它应该是一个等腰三角形。为什么这个案例值得关注它展示了理论方法在实际工程中的威力揭示了边界条件测试的重要性证明了系统化的测试设计能发现意外缺陷2. 因果图法实战从需求到测试用例2.1 需求分析与因果识别首先我们需要明确三角形问题的因果关系原因输入条件边长a、b、c都在1-200范围内任意两边之和大于第三边有且只有两边相等ab≠c或ac≠b或bc≠a三边全等abc结果输出不构成三角形普通三角形等腰三角形等边三角形2.2 构建因果图与决策表将上述因果关系转化为决策表我们得到以下关键测试组合条件组合有效输入范围两边和第三边两边相等三边相等预期输出1是是否否普通三角形2是是是否等腰三角形3是是是是等边三角形4否---不构成三角形5是否--不构成三角形2.3 设计测试用例基于决策表我们设计了以下关键测试用例// 测试用例示例 TEST_CASE(三角形类型判断) { // 普通三角形 CHECK(triangleType(3, 4, 5) 普通三角形); // 等腰三角形边界情况 CHECK(triangleType(200, 200, 1) 等腰三角形); // 等边三角形 CHECK(triangleType(100, 100, 100) 等边三角形); // 不构成三角形的情况 CHECK(triangleType(1, 2, 3) 不构成三角形); CHECK(triangleType(0, 100, 100) 不构成三角形); }3. BUG狩猎定位与分析在执行测试用例时发现了一个有趣的异常测试用例输入200, 200, 1预期输出等腰三角形实际输出普通三角形通过代码审查发现问题出在程序的排序逻辑上void triangle(int a, int b, int c) { // 排序逻辑 if(a b) { swap(a, b); } if(a c) { swap(a, c); } if(b c) { swap(b, c); } // 判断逻辑 if(a b) { if(a ! c) printf(等腰三角\n); else printf(等边三角\n); } else { printf(普通三角形\n); // 问题出在这里 } }问题根源程序先对边长进行排序使a ≤ b ≤ c然后仅检查a b的情况忽略了b c的可能性对于输入(200,200,1)排序后变为(1,200,200)由于1 ! 200直接进入else分支错误判断为普通三角形4. 修复方案与代码优化4.1 修复逻辑缺陷修改后的判断逻辑应该考虑所有两边相等的情况void triangle(int a, int b, int c) { // 排序逻辑保持不变 if(a b) { swap(a, b); } if(a c) { swap(a, c); } if(b c) { swap(b, c); } // 改进的判断逻辑 if(a b || b c) { // 检查任意两边相等 if(a b b c) { printf(等边三角\n); } else { printf(等腰三角\n); } } else { printf(普通三角形\n); } }4.2 防御性编程增强为了进一步提高代码健壮性建议添加以下改进输入验证if(scanf(%d%d%d, a, b, c) ! 3) { printf(输入无效\n); return; }边界检查优化#define MIN_SIDE 1 #define MAX_SIDE 200 if(a MIN_SIDE || a MAX_SIDE || b MIN_SIDE || b MAX_SIDE || c MIN_SIDE || c MAX_SIDE) { printf(边长超出有效范围\n); return; }浮点数支持可选void triangle(double a, double b, double c) { // 使用浮点数比较的epsilon方法 const double eps 1e-6; bool ab_eq fabs(a - b) eps; bool bc_eq fabs(b - c) eps; bool ac_eq fabs(a - c) eps; if(ab_eq || bc_eq || ac_eq) { if(ab_eq bc_eq) { printf(等边三角\n); } else { printf(等腰三角\n); } } else { printf(普通三角形\n); } }5. 测试策略的深层思考5.1 因果图法的优势与局限优势系统性地覆盖所有输入组合避免测试用例的随意性特别适合条件复杂的决策逻辑局限对于输入条件过多的情况组合会爆炸性增长需要准确识别所有因果关系对时序相关的场景支持有限5.2 三角形问题的完整测试策略一个健壮的测试计划应该包括等价类划分有效等价类1 ≤ 边长 ≤ 200无效等价类边长 1 或 200边界值分析最小值边界0,1,2最大值边界199,200,201特殊值MAX/2, MAX/21错误推测非数字输入负数输入三个零输入浮点数输入组合测试# 使用pytest生成组合测试 pytest.mark.parametrize(a, [0, 1, 100, 199, 200, 201]) pytest.mark.parametrize(b, [0, 1, 100, 199, 200, 201]) pytest.mark.parametrize(c, [0, 1, 100, 199, 200, 201]) def test_triangle_combinations(a, b, c): result triangle_type(a, b, c) if not (1 a 200 and 1 b 200 and 1 c 200): assert result 不构成三角形 elif a b c or a c b or b c a: assert result 不构成三角形 elif a b c: assert result 等边三角形 elif a b or b c or a c: assert result 等腰三角形 else: assert result 普通三角形6. 从测试到质量保障的进阶之路6.1 自动化测试集成将因果图法生成的测试用例集成到CI/CD流水线中# .github/workflows/triangle-test.yml name: Triangle Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv2 - name: Set up Python uses: actions/setup-pythonv2 with: python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip pip install pytest - name: Test with pytest run: | pytest test_triangle.py -v6.2 测试覆盖率提升使用覆盖率工具确保没有遗漏的分支# 安装覆盖率工具 pip install pytest-cov # 运行测试并收集覆盖率 pytest --covtriangle test_triangle.py # 生成HTML报告 pytest --covtriangle --cov-reporthtml test_triangle.py关键覆盖率指标语句覆盖率100%分支覆盖率100%条件覆盖率100%6.3 性能测试考量对于高频调用的三角形判断函数还需要考虑性能// 性能测试示例 #include chrono #include vector void benchmark() { const int iterations 1000000; std::vectorstd::tupleint, int, int testCases { {3,4,5}, {2,2,3}, {5,5,5}, {1,2,3} }; auto start std::chrono::high_resolution_clock::now(); for(int i 0; i iterations; i) { for(auto [a,b,c] : testCases) { triangle(a, b, c); } } auto end std::chrono::high_resolution_clock::now(); auto duration std::chrono::duration_caststd::chrono::milliseconds(end - start); std::cout Executed iterations iterations in duration.count() ms\n; }7. 代码复盘与最佳实践7.1 原始代码问题总结逻辑缺陷等腰三角形判断不完整错误处理不足代码风格问题混合了C和C的I/O缺乏模块化输出信息不一致可测试性问题函数有副作用直接打印难以进行自动化验证7.2 重构后的完整实现// triangle.hpp #pragma once #include string #include algorithm enum class TriangleType { INVALID, SCALENE, ISOSCELES, EQUILATERAL }; inline TriangleType classifyTriangle(int a, int b, int c) { // 参数排序 if(a b) std::swap(a, b); if(a c) std::swap(a, c); if(b c) std::swap(b, c); // 范围检查 constexpr int MIN 1; constexpr int MAX 200; if(a MIN || c MAX) return TriangleType::INVALID; // 三角形不等式 if(a b c) return TriangleType::INVALID; // 类型判断 if(a b b c) { return TriangleType::EQUILATERAL; } if(a b || b c) { return TriangleType::ISOSCELES; } return TriangleType::SCALENE; } inline std::string triangleTypeToString(TriangleType type) { switch(type) { case TriangleType::INVALID: return 不构成三角形; case TriangleType::SCALENE: return 普通三角形; case TriangleType::ISOSCELES: return 等腰三角形; case TriangleType::EQUILATERAL: return 等边三角形; default: return 未知类型; } }7.3 单元测试套件// test_triangle.cpp #define CATCH_CONFIG_MAIN #include catch.hpp #include triangle.hpp TEST_CASE(三角形分类) { SECTION(无效输入) { REQUIRE(classifyTriangle(0, 1, 1) TriangleType::INVALID); REQUIRE(classifyTriangle(201, 200, 200) TriangleType::INVALID); } SECTION(不构成三角形) { REQUIRE(classifyTriangle(1, 2, 3) TriangleType::INVALID); REQUIRE(classifyTriangle(5, 10, 25) TriangleType::INVALID); } SECTION(等边三角形) { REQUIRE(classifyTriangle(100, 100, 100) TriangleType::EQUILATERAL); } SECTION(等腰三角形) { REQUIRE(classifyTriangle(200, 200, 1) TriangleType::ISOSCELES); REQUIRE(classifyTriangle(5, 5, 8) TriangleType::ISOSCELES); REQUIRE(classifyTriangle(7, 10, 10) TriangleType::ISOSCELES); } SECTION(普通三角形) { REQUIRE(classifyTriangle(3, 4, 5) TriangleType::SCALENE); REQUIRE(classifyTriangle(5, 12, 13) TriangleType::SCALENE); } }
从实验报告到真实BUG:我用因果图法给‘三角形判断’程序挖出了隐藏缺陷(附完整C++/C代码复盘)
发布时间:2026/6/3 14:13:32
从实验报告到真实BUG我用因果图法给‘三角形判断’程序挖出了隐藏缺陷附完整C/C代码复盘1. 黑盒测试的艺术当理论遇上实战在软件测试的世界里黑盒测试就像一场精心设计的侦探游戏。我们不需要知道代码内部的实现细节而是通过输入和输出的蛛丝马迹来验证程序是否按照预期工作。而因果图法就是这场游戏中最犀利的破案工具之一。最近我在一个看似简单的三角形判断程序中发现了一个教科书级别的隐藏缺陷。这个程序的功能是根据用户输入的三个边长判断它们能否构成三角形以及构成什么类型的三角形普通、等腰或等边。表面上看这个程序通过了常规测试但在使用因果图法系统分析后我发现了一个关键漏洞——当输入(200,200,1)时程序错误地将其判断为普通三角形而实际上它应该是一个等腰三角形。为什么这个案例值得关注它展示了理论方法在实际工程中的威力揭示了边界条件测试的重要性证明了系统化的测试设计能发现意外缺陷2. 因果图法实战从需求到测试用例2.1 需求分析与因果识别首先我们需要明确三角形问题的因果关系原因输入条件边长a、b、c都在1-200范围内任意两边之和大于第三边有且只有两边相等ab≠c或ac≠b或bc≠a三边全等abc结果输出不构成三角形普通三角形等腰三角形等边三角形2.2 构建因果图与决策表将上述因果关系转化为决策表我们得到以下关键测试组合条件组合有效输入范围两边和第三边两边相等三边相等预期输出1是是否否普通三角形2是是是否等腰三角形3是是是是等边三角形4否---不构成三角形5是否--不构成三角形2.3 设计测试用例基于决策表我们设计了以下关键测试用例// 测试用例示例 TEST_CASE(三角形类型判断) { // 普通三角形 CHECK(triangleType(3, 4, 5) 普通三角形); // 等腰三角形边界情况 CHECK(triangleType(200, 200, 1) 等腰三角形); // 等边三角形 CHECK(triangleType(100, 100, 100) 等边三角形); // 不构成三角形的情况 CHECK(triangleType(1, 2, 3) 不构成三角形); CHECK(triangleType(0, 100, 100) 不构成三角形); }3. BUG狩猎定位与分析在执行测试用例时发现了一个有趣的异常测试用例输入200, 200, 1预期输出等腰三角形实际输出普通三角形通过代码审查发现问题出在程序的排序逻辑上void triangle(int a, int b, int c) { // 排序逻辑 if(a b) { swap(a, b); } if(a c) { swap(a, c); } if(b c) { swap(b, c); } // 判断逻辑 if(a b) { if(a ! c) printf(等腰三角\n); else printf(等边三角\n); } else { printf(普通三角形\n); // 问题出在这里 } }问题根源程序先对边长进行排序使a ≤ b ≤ c然后仅检查a b的情况忽略了b c的可能性对于输入(200,200,1)排序后变为(1,200,200)由于1 ! 200直接进入else分支错误判断为普通三角形4. 修复方案与代码优化4.1 修复逻辑缺陷修改后的判断逻辑应该考虑所有两边相等的情况void triangle(int a, int b, int c) { // 排序逻辑保持不变 if(a b) { swap(a, b); } if(a c) { swap(a, c); } if(b c) { swap(b, c); } // 改进的判断逻辑 if(a b || b c) { // 检查任意两边相等 if(a b b c) { printf(等边三角\n); } else { printf(等腰三角\n); } } else { printf(普通三角形\n); } }4.2 防御性编程增强为了进一步提高代码健壮性建议添加以下改进输入验证if(scanf(%d%d%d, a, b, c) ! 3) { printf(输入无效\n); return; }边界检查优化#define MIN_SIDE 1 #define MAX_SIDE 200 if(a MIN_SIDE || a MAX_SIDE || b MIN_SIDE || b MAX_SIDE || c MIN_SIDE || c MAX_SIDE) { printf(边长超出有效范围\n); return; }浮点数支持可选void triangle(double a, double b, double c) { // 使用浮点数比较的epsilon方法 const double eps 1e-6; bool ab_eq fabs(a - b) eps; bool bc_eq fabs(b - c) eps; bool ac_eq fabs(a - c) eps; if(ab_eq || bc_eq || ac_eq) { if(ab_eq bc_eq) { printf(等边三角\n); } else { printf(等腰三角\n); } } else { printf(普通三角形\n); } }5. 测试策略的深层思考5.1 因果图法的优势与局限优势系统性地覆盖所有输入组合避免测试用例的随意性特别适合条件复杂的决策逻辑局限对于输入条件过多的情况组合会爆炸性增长需要准确识别所有因果关系对时序相关的场景支持有限5.2 三角形问题的完整测试策略一个健壮的测试计划应该包括等价类划分有效等价类1 ≤ 边长 ≤ 200无效等价类边长 1 或 200边界值分析最小值边界0,1,2最大值边界199,200,201特殊值MAX/2, MAX/21错误推测非数字输入负数输入三个零输入浮点数输入组合测试# 使用pytest生成组合测试 pytest.mark.parametrize(a, [0, 1, 100, 199, 200, 201]) pytest.mark.parametrize(b, [0, 1, 100, 199, 200, 201]) pytest.mark.parametrize(c, [0, 1, 100, 199, 200, 201]) def test_triangle_combinations(a, b, c): result triangle_type(a, b, c) if not (1 a 200 and 1 b 200 and 1 c 200): assert result 不构成三角形 elif a b c or a c b or b c a: assert result 不构成三角形 elif a b c: assert result 等边三角形 elif a b or b c or a c: assert result 等腰三角形 else: assert result 普通三角形6. 从测试到质量保障的进阶之路6.1 自动化测试集成将因果图法生成的测试用例集成到CI/CD流水线中# .github/workflows/triangle-test.yml name: Triangle Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv2 - name: Set up Python uses: actions/setup-pythonv2 with: python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip pip install pytest - name: Test with pytest run: | pytest test_triangle.py -v6.2 测试覆盖率提升使用覆盖率工具确保没有遗漏的分支# 安装覆盖率工具 pip install pytest-cov # 运行测试并收集覆盖率 pytest --covtriangle test_triangle.py # 生成HTML报告 pytest --covtriangle --cov-reporthtml test_triangle.py关键覆盖率指标语句覆盖率100%分支覆盖率100%条件覆盖率100%6.3 性能测试考量对于高频调用的三角形判断函数还需要考虑性能// 性能测试示例 #include chrono #include vector void benchmark() { const int iterations 1000000; std::vectorstd::tupleint, int, int testCases { {3,4,5}, {2,2,3}, {5,5,5}, {1,2,3} }; auto start std::chrono::high_resolution_clock::now(); for(int i 0; i iterations; i) { for(auto [a,b,c] : testCases) { triangle(a, b, c); } } auto end std::chrono::high_resolution_clock::now(); auto duration std::chrono::duration_caststd::chrono::milliseconds(end - start); std::cout Executed iterations iterations in duration.count() ms\n; }7. 代码复盘与最佳实践7.1 原始代码问题总结逻辑缺陷等腰三角形判断不完整错误处理不足代码风格问题混合了C和C的I/O缺乏模块化输出信息不一致可测试性问题函数有副作用直接打印难以进行自动化验证7.2 重构后的完整实现// triangle.hpp #pragma once #include string #include algorithm enum class TriangleType { INVALID, SCALENE, ISOSCELES, EQUILATERAL }; inline TriangleType classifyTriangle(int a, int b, int c) { // 参数排序 if(a b) std::swap(a, b); if(a c) std::swap(a, c); if(b c) std::swap(b, c); // 范围检查 constexpr int MIN 1; constexpr int MAX 200; if(a MIN || c MAX) return TriangleType::INVALID; // 三角形不等式 if(a b c) return TriangleType::INVALID; // 类型判断 if(a b b c) { return TriangleType::EQUILATERAL; } if(a b || b c) { return TriangleType::ISOSCELES; } return TriangleType::SCALENE; } inline std::string triangleTypeToString(TriangleType type) { switch(type) { case TriangleType::INVALID: return 不构成三角形; case TriangleType::SCALENE: return 普通三角形; case TriangleType::ISOSCELES: return 等腰三角形; case TriangleType::EQUILATERAL: return 等边三角形; default: return 未知类型; } }7.3 单元测试套件// test_triangle.cpp #define CATCH_CONFIG_MAIN #include catch.hpp #include triangle.hpp TEST_CASE(三角形分类) { SECTION(无效输入) { REQUIRE(classifyTriangle(0, 1, 1) TriangleType::INVALID); REQUIRE(classifyTriangle(201, 200, 200) TriangleType::INVALID); } SECTION(不构成三角形) { REQUIRE(classifyTriangle(1, 2, 3) TriangleType::INVALID); REQUIRE(classifyTriangle(5, 10, 25) TriangleType::INVALID); } SECTION(等边三角形) { REQUIRE(classifyTriangle(100, 100, 100) TriangleType::EQUILATERAL); } SECTION(等腰三角形) { REQUIRE(classifyTriangle(200, 200, 1) TriangleType::ISOSCELES); REQUIRE(classifyTriangle(5, 5, 8) TriangleType::ISOSCELES); REQUIRE(classifyTriangle(7, 10, 10) TriangleType::ISOSCELES); } SECTION(普通三角形) { REQUIRE(classifyTriangle(3, 4, 5) TriangleType::SCALENE); REQUIRE(classifyTriangle(5, 12, 13) TriangleType::SCALENE); } }