news 2026/10/19 1:05:22

Tessy单元测试提高软件质量:嵌入式C函数级测试与桩机制实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Tessy单元测试提高软件质量:嵌入式C函数级测试与桩机制实战

简介:这份PDF白皮书聚焦单元测试与分类树方法,面向嵌入式软件开发者、测试工程师及关注功能安全与代码质量的团队,帮助读者理解如何借助Tessy工具系统化提升软件可靠性。资源共1个PDF文件,压缩包约1.87MB,内容为Hitex出品的英文技术文档,结构完整、便于通读。文档从单元测试的定义与重要性切入,结合IEC 61508、ISO 26262、Automotive SPICE等标准说明模块测试与单元测试的适用场景,并给出Tessy的实操示例,涵盖被测单元接口、初始测试用例、测试执行及回归测试等环节;后半部分系统讲解分类树方法,包括识别测试相关方面、分类、重复分类直至在组合表中指定测试用例的完整流程。目前已有4860人学习,适合希望掌握单元测试方法论、分类树用例设计及Tessy工具应用的读者参考。

1. Tessy单元测试提高软件质量:从“测了就行”到“测得准”的分水岭

很多做嵌入式C的朋友都有过这种经历:代码在硬件上跑通了,功能看着也对,但一到集成阶段就冒出各种边界问题,回头查发现是某个函数在入参为极值时行为异常。这类问题靠人工评审很难穷尽,靠在线调试又太依赖运气。Tessy单元测试提高软件质量这件事,核心不是“多跑几条用例”,而是把函数级别的输入输出关系用可重复、可追溯的方式固化下来,让每次代码改动都能被自动验证一遍。它适合谁?适合那些已经能写出可编译的C代码、但对“质量”还停留在“功能正常”层面的嵌入式开发者,尤其是做汽车电子、工业控制、医疗设备这类对可靠性有硬要求的团队。Tessy在这类场景里的价值,是把测试从“事后补救”变成“改代码就触发”的日常动作。

2. 先搞清楚Tessy到底在测什么:函数级隔离与桩机制

2.1 单元测试在嵌入式C里的特殊约束

桌面端的单元测试可以随便链接标准库、随便申请内存,但嵌入式C不行。一个函数可能直接操作寄存器、调用硬件抽象层、依赖全局状态。Tessy的切入点很明确:它不要求你把代码改成“可测试”的架构,而是通过插桩和桩函数把被测函数从它的运行环境里“摘”出来。你给Tessy一个C文件,它分析出所有函数,你选中其中一个作为被测对象,剩下的外部依赖——无论是同文件的其他函数还是别的模块的接口——都可以被替换成桩。这个机制决定了Tessy的测试粒度是函数级,不是模块级也不是系统级。

常见做法是先用Tessy的工程向导导入源文件,它会自动生成函数列表和调用关系图。这时候你会看到哪些函数是“叶子函数”(不调用其他自定义函数),哪些是“中间层”。叶子函数最适合先测,因为桩最少。中间层函数需要为它调用的每个外部函数写桩,桩的行为决定了测试的覆盖范围。

2.2 桩函数不是“随便返回一个值”

很多人第一次用Tessy写桩,就是让桩函数返回一个固定值,然后看被测函数输出对不对。这只能验证一条路径。真正的桩要能模拟三种行为:返回值、输出参数、全局状态变化。比如被测函数调用了一个read_sensor(),桩不仅要返回一个温度值,还要考虑这个值在边界附近时被测函数的分支走向。Tessy的桩配置界面里可以给同一个桩函数设置多组“调用序列”,第一次调用返回A,第二次返回B,用来模拟连续调用下的状态迁移。

/* 被测函数示例:根据传感器读数计算告警等级 */ int calc_alarm_level(int sensor_value) { if (sensor_value < 0) return -1; /* 无效值 */ if (sensor_value > 100) return 2; /* 超限 */ if (sensor_value > 80) return 1; /* 预警 */ return 0; /* 正常 */ } /* Tessy桩配置中为read_sensor设置调用序列 */ /* 第一次调用返回-5,第二次返回85,第三次返回120 */ /* 对应验证被测函数的四条分支 */

上面这段代码里,calc_alarm_level本身不依赖外部调用,可以直接测。但如果它内部调用了read_sensor(),你就需要在Tessy里为read_sensor建桩,并设置返回值序列。参数说明:sensor_value的边界是0、80、100,测试用例至少要覆盖-1、0、80、81、100、101这几个点。Tessy的“边界值分析”功能可以自动生成这些用例,但自动生成的用例需要你手动确认期望输出,不能直接信任。

2.3 从C文件到Tessy工程的最小操作路径

新建Tessy工程后,第一步是选择“New Test Object”,导入你的C源文件。Tessy会解析出所有函数,你勾选要测的那个。第二步是配置编译环境,Tessy需要知道你的交叉编译器路径和头文件搜索路径,否则解析会失败。第三步是“Generate Stubs”,Tessy会列出所有未解析的外部符号,你逐个决定是忽略还是建桩。第四步是写测试用例,每个用例包含输入参数、桩的返回值序列、期望的被测函数返回值和输出参数。第五步是运行并看覆盖率报告。

注意:Tessy的覆盖率统计依赖编译时的插桩选项,如果编译器优化等级开得太高(比如-O2),插桩点可能被优化掉,覆盖率会虚低。建议测试时用-O0或-O1。

3. 把测试用例写“厚”:等价类、边界值与桩序列的组合

3.1 等价类划分在Tessy里的落地方式

等价类划分是老生常谈,但在Tessy里怎么落地?不是让你在文档里写“有效等价类:0到100”,而是要在Tessy的测试用例表里真的把每个等价类的代表值填进去。Tessy的“Test Case Editor”是一个表格,每一行是一个用例,列包括输入参数、桩配置、期望输出。你可以用“Parameter Variation”功能批量生成用例,比如让某个参数在[min, max]区间内按步长生成,但步长不能代替边界值,边界点必须单独加。

我一般会先手动列出所有输入参数的边界点,然后在Tessy里建一个“边界用例集”,再建一个“典型值用例集”。边界用例集覆盖每个参数的min-1、min、min+1、max-1、max、max+1。典型值用例集覆盖正常业务场景下的几个代表值。两个集合跑完,再看覆盖率报告里哪些分支没走到,针对性补用例。

3.2 桩的调用序列怎么配才不翻车

桩的调用序列是Tessy里最容易配错的地方。假设被测函数内部有一个循环,每次循环都调用get_next_item(),循环次数取决于某个输入参数。如果你只给桩配了一个返回值,循环第二次调用时桩还是返回同一个值,可能导致死循环或者逻辑错误。正确做法是在桩配置里设置“Return Value Sequence”,按调用次数依次返回不同的值,并设置一个“Default Return Value”作为序列耗尽后的兜底。

/* 被测函数:遍历链表并累加有效值 */ int sum_valid_items(Node* head, int threshold) { int sum = 0; Node* cur = head; while (cur != NULL) { int val = get_item_value(cur); /* 外部调用,需要桩 */ if (val > threshold) { sum += val; } cur = get_next_node(cur); /* 外部调用,需要桩 */ } return sum; }

在这个例子里,get_item_value和get_next_node都需要桩。get_next_node的桩要返回一个节点指针序列,最后一个返回NULL表示链表结束。Tessy里可以这样配:第一次调用返回node1,第二次返回node2,第三次返回NULL。get_item_value的桩则根据传入的节点指针返回不同的值,这需要桩函数能识别参数——Tessy支持“Parameter-dependent Return”,你可以为不同的输入参数值配置不同的返回值。

参数说明:threshold的边界是链表里所有可能的值,测试时要确保有值刚好等于threshold、刚好大于、刚好小于。head为NULL的情况要单独一个用例,验证函数返回0而不是崩溃。

3.3 覆盖率报告里哪些数字值得看

Tessy跑完测试后会生成覆盖率报告,常见的有语句覆盖率(Statement Coverage)、分支覆盖率(Branch Coverage)、条件覆盖率(Condition Coverage)和MC/DC。对于嵌入式C,我建议至少做到分支覆盖率100%,条件覆盖率尽量高。MC/DC在安全关键领域是硬要求,但写起来很费时间,每个条件都要独立影响一次判断结果。

看报告时不要只看百分比,要点开未覆盖的分支看具体是哪一行。有时候未覆盖是因为代码里有防御性分支(比如if (ptr == NULL) return;),这种分支在正常测试里确实走不到,但你不能删掉它,只能补一个专门传NULL的用例。Tessy的报告可以导出为HTML或PDF,方便归档。

4. 避坑:Tessy单元测试提高软件质量过程中最容易翻车的五件事

4.1 现象:覆盖率跑出来是0%,但测试用例明明执行了

原因通常是编译插桩没开,或者Tessy用的编译器和实际工程用的编译器不是同一个。Tessy需要在编译时插入覆盖率统计代码,如果它调用的编译器路径指向了一个没有插桩能力的版本,覆盖率就采集不到。解决方法是检查Tessy工程设置里的“Compiler”配置,确认插桩选项(通常是--coverage或类似标志)已经加到编译命令里。另外,如果源文件里有#pragma禁用了某些优化,也可能影响插桩。

4.2 现象:桩函数被调用了,但被测函数拿到的返回值不对

这通常是桩的“调用约定”没配对。嵌入式C里常见的有__cdecl、__stdcall,还有编译器特有的属性。如果桩函数的声明和原函数不一致,参数传递就会错位。解决方法是让Tessy自动生成桩的声明,不要手写。Tessy会根据解析出的函数原型生成桩模板,你只需要填函数体。手写桩声明是血泪教训,十有八九会在参数类型上翻车。

4.3 现象:测试用例单独跑通过,批量跑就失败

这是全局状态污染。被测函数可能修改了某个全局变量,前一个用例改了之后没恢复,后一个用例读到的就是脏数据。Tessy本身不自动重置全局变量,你需要在每个用例的“Initialization”部分手动把全局变量设回初始值。或者用Tessy的“Test Environment”功能,为每个用例创建独立的环境副本。常见做法是在测试套件的Setup阶段统一初始化所有全局变量,Teardown阶段再清理一遍。

4.4 现象:Tessy解析C文件时报“Unresolved symbol”,但那个符号在别的文件里定义了

Tessy只解析你导入的源文件,不会自动去链接其他文件。如果被测函数调用了另一个C文件里的函数,Tessy会把它当作未解析符号。你有两个选择:要么把那个C文件也导入Tessy工程,要么为那个函数建桩。如果那个函数是标准库函数(比如memcpy),可以在Tessy的“Library Stubs”里启用对应的桩。如果那个函数来自第三方库且没有源码,只能建桩,桩的行为要参考库的文档。

4.5 现象:测试通过了,但代码改了一行之后测试全挂

这是测试用例和实现细节耦合太紧。比如你在用例里硬编码了某个中间变量的值,而那个变量是内部实现的一部分,不是接口。好的单元测试应该只依赖函数的输入参数和返回值,不依赖内部状态。Tessy的“Black-box Testing”模式可以帮你隐藏内部变量,只暴露接口。如果发现测试用例里引用了非接口变量,考虑重构用例,或者把那个变量通过桩的返回值间接控制。

5. 让Tessy测试真正提高质量:回归、持续集成与测试坏味道的识别

5.1 把Tessy测试挂到持续集成流水线里

Tessy支持命令行执行,这是它区别于很多图形化测试工具的关键优势。你可以在CI脚本里调用Tessy的命令行接口,传入工程文件和测试套件,让它自动跑完并生成报告。如果覆盖率低于阈值,CI就失败。这样每次代码提交都会触发一轮单元测试,改坏了立刻知道。

# Tessy命令行执行示例(路径和参数名以实际安装为准) tessy_cli --project ./test_project.tpr \ --test-suite ./unit_tests.tsu \ --report ./reports/coverage.html \ --coverage-threshold 80 \ --fail-on-threshold

参数说明:--project指定Tessy工程文件,--test-suite指定要执行的测试套件,--report指定报告输出路径,--coverage-threshold设置覆盖率下限,--fail-on-threshold表示低于阈值时返回非零退出码。CI系统根据退出码判断是否继续后续步骤。这个脚本可以放在代码仓库的ci/目录下,和源码一起版本管理。

5.2 识别测试坏味道:什么时候该重构测试而不是加用例

测试代码也是代码,也会腐化。常见的坏味道有三种:一是“断言过多”,一个用例里塞了十几个断言,失败时不知道是哪个输入导致的;二是“桩配置过长”,一个桩配了二十个返回值序列,说明被测函数的逻辑太复杂,应该拆函数;三是“用例之间互相依赖”,用例B必须在用例A之后跑才能通过,这说明全局状态没清理干净。

遇到这些情况,优先重构被测代码,而不是在测试端打补丁。Tessy的测试用例是跟着函数走的,函数拆小了,用例自然就简单了。我一般会设一个规矩:单个用例的断言不超过5个,单个桩的返回值序列不超过10步。超了就回头看代码。

5.3 一个具体技巧:用Tessy的“Test Case Generation”做探索性测试

Tessy有一个自动生成测试用例的功能,基于你设定的参数范围和覆盖率目标,它会自动生成一批用例。这个功能不能替代手工用例,但可以用来做探索性测试——先让Tessy自动生成一批,跑一遍看覆盖率,然后针对未覆盖的分支手工补用例。自动生成的用例往往在边界值上不够精确,但能帮你发现一些你没想到的输入组合。

我通常的流程是:手工写核心用例覆盖主要业务逻辑,然后让Tessy自动生成补充用例,最后人工审查自动生成的用例,把冗余的删掉,把有价值的保留并加上期望输出。这样既保证了覆盖率,又不会让测试套件膨胀到无法维护。

5.4 验证测试本身的有效性:变异测试的思路

你怎么知道你的测试用例真的能抓到bug?一个实用的办法是手动注入变异:把被测函数里的某个>改成>=,或者把某个+改成-,然后跑测试。如果测试全绿,说明你的用例没覆盖到那个分支。Tessy本身不直接支持变异测试,但你可以手动改代码然后重新跑。这个动作不需要频繁做,但在关键模块上线前做一轮,能发现不少测试盲区。

我自己的习惯是:每次修完一个bug,先写一个能复现该bug的测试用例,确认它失败,然后再修代码,确认测试通过。这个顺序不能反。先修代码再补测试,你永远不知道测试是不是真的在测那个bug。这个习惯坚持下来,测试套件的质量会越来越高,Tessy单元测试提高软件质量这句话才不是空话。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 21:34:13

多线程服务器文件描述符传递:从裸int到SCM_RIGHTS的正确姿势

1. 一场典型的多线程服务器事故现场先看一段很常见的代码&#xff0c;很多刚开始写Linux并发服务器的同学&#xff0c;第一版往往长这样&#xff1a;// 主线程 while (1) {int connfd accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);thread_pool_add_task(co…

作者头像 李华
网站建设 2026/10/17 20:54:50

XXE漏洞实战:从文件读取到内网渗透的利用与防御

做授权渗透测试时&#xff0c;我有个习惯&#xff1a;只要接口里出现了XML字样&#xff0c;不管它带不带XXE三个字母&#xff0c;都会顺手测一下。原因很简单——XXE漏洞不是那种需要复杂业务逻辑才能触发的洞&#xff0c;它往往是一个解析器默认配置就坑了一个系统的洞。从一个…

作者头像 李华
网站建设 2026/10/18 11:20:35

NCS8801S芯片RGB/LVDS转EDP:选型、配置与调试实战指南

简介&#xff1a;NCS8801S 是一颗低功耗、高性能的 RGB/LVDS 转 eDP 桥接芯片&#xff0c;面向从事手机、平板、笔记本等移动设备显示方案设计的硬件工程师与嵌入式开发者。它可将单/双通道 LVDS 或 18/24bit RGB 信号转换为 1/2/4-lane eDP 输出&#xff0c;支持 1.62/2.7Gbps…

作者头像 李华
网站建设 2026/10/18 1:01:44

ProtoBuf转EmmyLua注解:让VS Code在Lua协议开发中实现类型补全

在游戏客户端开发里&#xff0c;用ProtoBuf定义协议、用Lua写业务逻辑的组合非常常见。可一旦proto里的message到了Lua侧&#xff0c;IDE完全不认识它&#xff0c;字段名、嵌套层级、枚举值全靠脑子记&#xff0c;写错只能等运行时报错。所以我把ProtoBuf自动转成EmmyLua注解AP…

作者头像 李华
网站建设 2026/10/18 0:48:12

diagram-design:用图即代码与图形语言构建可维护的图表体系

1. 从一张草图到一套系统&#xff1a;diagram-design 到底在解决什么问题第一次听到 diagram-design 这个词&#xff0c;很多人会下意识觉得它就是个“画图工具”或者“图表模板库”。但真正在项目里被图表折磨过的人会明白&#xff0c;它要解决的根本不是“怎么画”&#xff0…

作者头像 李华
网站建设 2026/10/18 1:01:47

从背八股到扛住连环追问:大厂面试追问的六个底层套路

很多同学备战后端面试&#xff0c;最喜欢干的一件事就是背八股。什么“MySQL为什么用B树”“Redis为什么快”“CAP定理怎么理解”&#xff0c;背得滚瓜烂熟&#xff0c;张口就来。但一到真实面试&#xff0c;尤其是大厂二面三面&#xff0c;往往不是被基础题难倒&#xff0c;而…

作者头像 李华