网站首页 > 技术文章 正文
还在写"测试困难户"代码?掌握可测试性设计,让你的代码质量飞升,bug率暴降90%!
你有没有遇到过这样的尴尬:
- 写完代码发现很难测试
- bug修了一个又冒出三个
- 测试工程师天天找你改bug
- 代码一改就心惊胆战,不知道会影响什么
今天就来聊聊可测试性软件设计,让你从"bug制造机"变成"质量大师"!
什么是可测试性?
定义很简单
可测试性 = 你的代码写完后,能够方便、全面地进行自测的能力
为什么这么重要?
传统开发流程的痛苦
开发 → 提交测试 → 发现bug → 修复 → 再测试 → 又发现bug → ...
问题:
- bug修复周期长
- 反复打断开发节奏
- 测试工程师和开发工程师都累
- 项目进度拖延
可测试性设计的威力
开发 → 充分自测 → 提交测试 → 一次通过 → 继续下个功能
收益:
- 大幅减少bug数量
- 提高开发效率
- 改善团队关系
- 项目进度可控
可测试性设计的核心原则
原则一:依赖外部化
难以测试的设计
测试困难:
- 无法控制Flash中的配置值
- 无法模拟不同的系统时间
- 无法模拟各种传感器状态
- 测试覆盖率极低
易于测试的设计
测试友好:
- 可以测试各种参数组合
- 覆盖边界条件
- 模拟异常情况
- 测试覆盖率接近100%
原则二:单一职责
职责混乱的函数
职责清晰的设计
单元测试框架集成
选择合适的测试框架
Unity框架(推荐)
Unity简介:
Unity是一个专为C语言设计的轻量级单元测试框架,特别适合嵌入式系统开发。它提供了丰富的断言宏,支持多种数据类型测试,并且具有极小的内存占用(通常只有几KB),非常适合资源受限的嵌入式环境。
主要特点:
- 轻量级:核心代码仅几百行,内存占用极小
- 易集成:单头文件设计,无需复杂配置
- 丰富断言:支持整数、浮点、字符串、数组等多种类型
- 跨平台:支持各种编译器和目标平台
- 详细报告:提供测试结果统计和失败信息
下载链接:
- GitHub官方仓库:https://github.com/ThrowTheSwitch/Unity
Unity测试流程架构:
常用断言宏:
- TEST_ASSERT_EQUAL(expected, actual) - 整数相等
- TEST_ASSERT_EQUAL_FLOAT(expected, actual, delta) - 浮点数相等
- TEST_ASSERT_TRUE(condition) - 条件为真
- TEST_ASSERT_FALSE(condition) - 条件为假
- TEST_ASSERT_NULL(pointer) - 指针为空
- TEST_ASSERT_NOT_NULL(pointer) - 指针非空
- TEST_ASSERT_EQUAL_STRING(expected, actual) - 字符串相等
使用例子:
#include "unity.h"
// 测试用例
void test_convert_sensor_data(void) {
// 正常值测试
TEST_ASSERT_EQUAL_FLOAT(25.0, convert_sensor_data(650));
// 边界值测试
TEST_ASSERT_EQUAL_FLOAT(-40.0, convert_sensor_data(0));
TEST_ASSERT_EQUAL_FLOAT(215.0, convert_sensor_data(2550));
// 负值测试
TEST_ASSERT_EQUAL_FLOAT(-50.0, convert_sensor_data(-100));
}
void test_is_sensor_data_valid(void) {
// 有效值测试
TEST_ASSERT_TRUE(is_sensor_data_valid(25.0));
TEST_ASSERT_TRUE(is_sensor_data_valid(-49.9));
TEST_ASSERT_TRUE(is_sensor_data_valid(149.9));
// 无效值测试
TEST_ASSERT_FALSE(is_sensor_data_valid(-50.1));
TEST_ASSERT_FALSE(is_sensor_data_valid(150.1));
TEST_ASSERT_FALSE(is_sensor_data_valid(NAN));
}
// 测试运行器
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_convert_sensor_data);
RUN_TEST(test_is_sensor_data_valid);
return UNITY_END();
}
串口测试命令系统
// 测试命令框架
typedef struct {
const char *cmd_name;
int (*test_func)(int argc, char *argv[]);
const char *description;
} test_command_t;
// 具体测试函数
int test_calculate_function(int argc, char *argv[]) {
if (argc != 3) {
printf("Usage: test_calc <a> <b> <expected>\n");
return -1;
}
int a = atoi(argv[0]);
int b = atoi(argv[1]);
int expected = atoi(argv[2]);
int result = calculate_result(a, b, 0, 1); // 简化测试
if (result == expected) {
printf("PASS: calc(%d, %d) = %d\n", a, b, result);
return 0;
} else {
printf("FAIL: calc(%d, %d) = %d, expected %d\n",
a, b, result, expected);
return -1;
}
}
// 测试命令表
static const test_command_t test_commands[] = {
{"calc", test_calculate_function, "Test calculation function"},
{"sensor", test_sensor_functions, "Test sensor operations"},
{"comm", test_communication, "Test communication protocol"},
};
// 串口命令处理
void process_test_command(const char *cmd_line) {
char *args[10];
int argc = parse_command(cmd_line, args, 10);
if (argc < 1) return;
for (int i = 0; i < ARRAY_SIZE(test_commands); i++) {
if (strcmp(args[0], test_commands[i].cmd_name) == 0) {
test_commands[i].test_func(argc - 1, &args[1]);
return;
}
}
printf("Unknown test command: %s\n", args[0]);
}
测试驱动开发(TDD)
TDD的开发流程
1. 写测试用例(红色阶段 - 测试失败)
2. 写最简实现(绿色阶段 - 测试通过)
3. 重构优化(重构阶段 - 保持测试通过)
实际应用示例
// 第一步:先写测试用例
void test_temperature_controller(void) {
// 测试加热逻辑
TEST_ASSERT_TRUE(should_heat(18.0, 20.0)); // 低于目标温度
TEST_ASSERT_FALSE(should_heat(22.0, 20.0)); // 高于目标温度
TEST_ASSERT_FALSE(should_heat(20.0, 20.0)); // 等于目标温度
// 测试边界条件
TEST_ASSERT_TRUE(should_heat(19.9, 20.0)); // 边界值
TEST_ASSERT_FALSE(should_heat(20.1, 20.0)); // 边界值
}
// 第二步:实现最简功能
bool should_heat(float current_temp, float target_temp) {
return current_temp < target_temp;
}
// 第三步:根据新需求扩展测试
void test_temperature_controller_with_hysteresis(void) {
// 添加滞回特性测试
TEST_ASSERT_TRUE(should_heat(18.0, 20.0)); // 开始加热
TEST_ASSERT_TRUE(should_heat(19.5, 20.0)); // 继续加热
TEST_ASSERT_FALSE(should_heat(20.5, 20.0)); // 停止加热
TEST_ASSERT_FALSE(should_heat(19.5, 20.0)); // 滞回,不重新加热
}
// 第四步:重构实现
typedef struct {
float target_temp;
float hysteresis;
bool heating_state;
} temp_controller_t;
bool should_heat(temp_controller_t *ctrl, float current_temp) {
if (!ctrl->heating_state) {
// 未加热状态:温度低于目标值时开始加热
if (current_temp < ctrl->target_temp - ctrl->hysteresis/2) {
ctrl->heating_state = true;
return true;
}
} else {
// 加热状态:温度高于目标值时停止加热
if (current_temp > ctrl->target_temp + ctrl->hysteresis/2) {
ctrl->heating_state = false;
return false;
}
return true; // 继续加热
}
return false;
}
可测试性设计检查清单
设计阶段检查
写在最后
可测试性设计不是额外的工作负担,而是提高代码质量、减少bug、提升开发效率的有效手段。
记住这个原则:在写代码之前,先思考如何测试这段代码。
这个思维转变会让你的代码设计变得更加合理,接口更加清晰,质量更加可靠。
互动讨论
你在项目中是如何进行代码测试的?
如果这篇文章改变了你对代码测试的认知,记得点赞收藏,让更多工程师看到可测试性设计的价值!
关注我,分享更多嵌入式软件工程和代码质量提升技巧!
猜你喜欢
- 2025-09-21 Python操作目录_python中目录的写法
- 2025-09-21 C语言的main函数的几种写法_c程序的main函数
- 2025-09-21 使用Redis如何实现查询附近的人?_redis查找
- 2025-09-21 Redis命令执行过程_redis-cli执行命令
- 2024-08-05 爬虫|快手短视频爬取经验分享(python爬取快手视频)
- 2024-08-05 如何用C++程序抓出来文件中的某一列 (类似awk)?
- 2024-08-05 详解Python sys.argv(详解杭州亚运会会徽和口号)
- 2024-08-05 「C语言重点难点精讲」关键字精讲1
- 2024-08-05 如何高效阅读代码?Linux大神拍了拍你并教给你这三个步骤
- 2024-08-05 一文带你了解操作系统核心概念(操作系统核心的主要作用)
- 最近发表
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- c语言min函数头文件 (77)
- asynccallback (87)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 无效的列索引 (74)