优秀的编程知识分享平台

网站首页 > 技术文章 正文

嵌入式可测试性设计的威力!_嵌入式测试怎么样

nanyue 2025-09-21 20:18:32 技术文章 1 ℃

还在写"测试困难户"代码?掌握可测试性设计,让你的代码质量飞升,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、提升开发效率的有效手段。

记住这个原则:在写代码之前,先思考如何测试这段代码。

这个思维转变会让你的代码设计变得更加合理,接口更加清晰,质量更加可靠。

互动讨论

你在项目中是如何进行代码测试的?

如果这篇文章改变了你对代码测试的认知,记得点赞收藏,让更多工程师看到可测试性设计的价值!


关注我,分享更多嵌入式软件工程和代码质量提升技巧!

Tags:

最近发表
标签列表