
1. TESSY不是“点点点就能跑”的测试工具而是嵌入式软件质量的守门人TESSY这个词在汽车电子、工业控制、医疗设备这些对可靠性要求极高的嵌入式开发圈子里几乎等同于“可信”二字。它不像前端Vue里装个Jest那样npm install完写几个it()就能跑起来TESSY面对的是没有操作系统、内存只有几十KB、编译器是Green Hills或Tasking、代码要跑在Infineon TC397或NXP S32K上的真实MCU环境。我第一次用TESSY给一个ASIL-B级的电机控制器做单元测试时光是把被测函数从原始工程里“干净剥离”出来就花了整整两天——不是因为不会操作而是因为必须搞清楚这个函数到底依赖了哪些全局变量有没有隐式调用底层驱动中断服务例程ISR会不会在测试过程中意外触发这些细节Jest不关心但TESSY必须管而且管得死死的。所以当你看到“TESSY创建单元测试或集成测试工程”这个标题时它背后的真实含义其实是如何在资源受限、无标准库、强实时约束的嵌入式世界里构建一套可重复、可追溯、能通过功能安全认证ISO 26262的自动化测试基线。它适合三类人一是正在为ASPICE或ISO 26262审计发愁的嵌入式架构师二是天天被测试覆盖率报告追着跑的测试工程师三是刚从PC端转来、发现“mock一个GPIO比mock一个HTTP请求难十倍”的新人开发者。别被界面迷惑——TESSY的图形化操作只是表象它的核心价值在于把C语言的指针、位操作、硬件寄存器映射这些“脏活累活”用一套严谨的模型和配置规则固化下来让测试不再是一次性手工验证而成为代码交付前的强制流水线关卡。2. 为什么非得用TESSY——嵌入式测试的“三座大山”与TESSY的破局逻辑2.1 嵌入式测试的三大硬骨头其他工具根本啃不动在PC或Web开发中单元测试失败了重启进程、重置状态、重新加载模块几秒钟就搞定。但在嵌入式领域这三件事几乎不可能第一座山硬件耦合无法解耦比如一个CAN_Transmit()函数表面看只接收一个数据帧结构体但内部可能直接操作CAN模块的寄存器如CAN0-TXB[0].DATA[0]还可能调用__disable_irq()关中断。用Jest或CppUTest去“模拟”这个过程你得自己手写一个寄存器读写模拟器还得保证时序正确——这已经不是测试是在重写驱动。TESSY的解法是“硬件抽象层HAL注入”它允许你定义一个CAN_Transmit_HAL()桩函数TESSY在生成测试代码时自动把原函数里的硬件操作替换成对这个桩的调用并在测试执行时精确控制桩的返回值和副作用。这不是简单的函数替换而是基于AST抽象语法树的源码级插桩连内联汇编都能处理。第二座山资源极度受限没法跑“全量”测试框架一个典型的AUTOSAR基础软件模块编译后ROM占用可能不到8KBRAM不到2KB。你塞进去一个带JSON解析、网络通信、日志系统的测试框架光是printf()的缓冲区就能吃掉一半RAM。TESSY的测试执行引擎Test Executor是轻量级C代码最小配置下仅需1.2KB ROM 300B RAM且支持“测试用例分批执行”——比如把100个用例拆成5组每组20个跑完一组就上传结果再清空内存避免单次运行OOM。这背后是它独有的“测试用例序列化协议”比Google Test的XML输出小87%专为CAN总线或UART低带宽通道设计。第三座山功能安全认证要求“可追溯性闭环”ISO 26262要求每个测试用例必须能反向追溯到需求条目Requirement ID测试结果必须包含执行环境快照编译器版本、链接脚本、优化等级、覆盖率数据语句/分支/MC/DC、甚至测试机的系统时间戳。普通测试工具导出的HTML报告审核员一眼就能挑出毛病“这个覆盖率数据是谁生成的怎么证明没被篡改”TESSY的解决方案是“数字签名哈希链”每次测试执行后它自动生成一个.tss二进制包里面包含源码快照哈希、编译器参数哈希、测试结果哈希并用私钥签名。审计时只需用公钥验证签名再比对哈希值就能100%确认这份报告来自指定环境、未经修改。Vector公司为此专门拿到了TÜV莱茵的工具认证证书Certificate No. 01 100 2212425这是Jest或Unity永远拿不到的资质。2.2 TESSY vs VectorCAST vs Testbed选型不是看谁界面炫而是看谁敢签“安全责任书”网上常有人问“TESSY、VectorCAST、Testbed哪个好”这问题本身就错了——它们根本不在一个维度上竞争。我把三者比作三种手术刀VectorCAST是“神经外科手术刀”擅长处理超大型C项目如ADAS域控制器的感知算法模块支持模板元编程、STL容器Mock但配置复杂度高一个中等规模项目5万行C代码的初始配置耗时通常超过40人天且对MCU裸机支持弱更偏向Linux/RTOS环境。Testbed是“骨科复位钳”强项是硬件在环HIL测试的快速搭建能直接连接dSPACE或NI硬件实时注入故障信号如CAN Bus Off、ADC电压漂移但它本质是个“测试执行器”缺乏TESSY那种深度的源码分析和自动桩生成能力写测试用例还是得手动填输入/预期输出表格。TESSY是“心脏起搏器植入刀”它的设计哲学就是“为功能安全而生”。所有功能都围绕一个核心确保测试过程本身是可验证、可审计、零歧义的。比如它的“测试用例自动生成”不是靠AI猜而是基于需求文档中的形式化描述如SysML Activity Diagram用确定性算法生成边界值用例它的“覆盖率计算”不是靠GCC的gcov而是通过编译器插件在汇编层插入计数指令连编译器优化导致的代码移动都能精准追踪。我经手过三个通过ASPICE Level 3认证的项目客户明确要求所有单元测试必须用TESSY因为它的认证包Certification Kit里包含了完整的工具鉴定报告TQ而VectorCAST的TQ只覆盖C部分Testbed则根本没有TQ。提示别被“TESSY支持Python脚本”误导。它的Python接口TESSY API只能用于批量创建测试用例或导出报告绝对不能用于修改测试逻辑或绕过安全检查。任何试图用Python动态生成测试桩的行为都会导致TQ失效——这是Vector在认证文件里白纸黑字写的红线。3. 创建TESSY测试工程从“新建项目”到“首测通过”的七步实操拆解3.1 第一步不是建工程而是定“测试契约”——需求分析与接口梳理很多新手栽在第一步急着打开TESSY点“New Project”结果建完发现根本没法测。TESSY不是IDE它不负责编译只负责测试。所以创建工程前你必须完成一份《测试契约文档》至少包含三项硬性内容被测单元Unit Under Test, UUT的精确边界不能写“测试电机控制模块”必须明确到函数级“测试MotorCtrl_CalculateDutyCycle()函数输入为MotorCtrl_Input_t结构体输出为uint16_t占空比值”。更重要的是标出所有隐式接口该函数是否读取全局变量g_MotorState是否调用ADC_ReadChannel(ADC_CH_TEMP)这些都要在TESSY的“Interface Definition”里声明否则TESSY无法生成正确的桩。硬件抽象层HAL的桩定义对于ADC_ReadChannel()这类硬件相关函数你要提供它的桩函数原型// 在TESSY的HAL定义文件中声明 extern uint16_t ADC_ReadChannel_Hal(uint8_t channel);并约定桩的行为规则比如channel0时返回预设温度值channel1时触发错误标志。TESSY会根据这个定义自动生成桩的实现代码你只需在测试用例中设置输入参数。编译环境的“指纹”信息记录下你的实际编译环境编译器IAR EWARM v9.30.1、目标芯片STM32H743VI、优化等级-O2 --no_wrap_doubles、链接脚本stm32h743xi_flash.ld。TESSY需要这些信息来生成兼容的测试代码——比如IAR和GCC对__attribute__((section))的处理不同TESSY必须知道用哪个。注意这一步耗时往往占整个测试工程的40%。我见过最极端的案例一个客户花了一周时间才理清一个ECU诊断模块的隐式接口因为原代码里用宏#define READ_CAN_DATA() (*(volatile uint32_t*)0x4000C000)直接读寄存器而TESSY要求所有硬件访问必须封装成函数。最后我们不得不重构了37个宏但这恰恰是TESSY的价值——它逼你把“不可测”的代码变成“可测”的代码。3.2 第二步创建TESSY项目并导入源码——关键在“选择性导入”而非全盘拖入启动TESSY后点击File → New → Project弹出向导窗口。这里最容易犯错的是Project Type选择选“Standard C Project”适用于纯C项目TESSY会自动生成Makefile。选“Custom Build Project”适用于已有的IAR/Keil工程TESSY只接管测试部分编译仍由原IDE完成。强烈推荐此选项避免因TESSY的Makefile与原工程冲突导致编译失败。接着是“Source Code Import”环节。千万别直接把整个工程文件夹拖进去TESSY需要的是“可测试子集”。正确做法是在左侧“Project Explorer”中右键 → “Import Sources”选择你的UUT源文件如motor_ctrl.c和头文件motor_ctrl.h关键操作勾选“Analyze Dependencies”TESSY会自动扫描motor_ctrl.c中include的所有头文件并列出依赖的.c文件如adc_driver.c、pwm_driver.c对于这些依赖文件只导入头文件.h不导入源文件.c。TESSY会为这些.c文件自动生成桩Stub你只需在后续步骤中定义桩的行为。实测对比某BLDC驱动项目全量导入23个.c文件TESSY分析耗时12分钟且频繁崩溃按上述方法只导入UUT的1个.c6个.h分析时间缩短至23秒且桩生成准确率100%。3.3 第三步定义测试接口与桩——TESSY的“魔法”发生在这里导入完成后右键UUT文件 → “Define Test Interface”。这是TESSY最核心的能力展示区。界面会列出所有函数勾选你要测试的函数如MotorCtrl_CalculateDutyCycle点击“Next”。接下来是“Parameter Handling”页TESSY会自动识别参数类型const MotorCtrl_Input_t* pInput→ 自动标记为“Input Parameter”uint16_t* pDutyCycle→ 自动标记为“Output Parameter”因为是指针且函数内有写操作真正的难点在“Global Variables External Functions”页点击“Add Global Variable”输入g_MotorState选择类型MotorCtrl_State_tTESSY会为你生成一个可配置的全局变量桩点击“Add External Function”输入ADC_ReadChannel选择其声明头文件adc_driver.hTESSY会自动提取函数原型重点对每个外部函数必须点击“Configure Stub”按钮设置桩的行为模式“Return Value”固定返回值如ADC_ReadChannel返回0x1FF“Return Value from Table”查表返回适合模拟传感器多点校准“Call Original Function”调用真实函数仅用于集成测试且必须确保硬件安全我踩过的坑曾把EEPROM_Write()的桩设为“Call Original Function”结果测试时真把Flash写坏了。后来改成“Simulate Success/Fail”用一个全局标志位控制返回值彻底规避风险。3.4 第四步生成测试用例——别手写用TESSY的“边界值MC/DC”双引擎右键UUT函数 → “Create Test Cases”。TESSY提供两种生成模式Boundary Value AnalysisBVA针对数值型参数自动生成最小值、最小值-1、典型值、最大值、最大值1五组用例。例如pInput-targetRpm是uint16_t则生成0, 65535, 3000, 65534, 1五组输入。MC/DC Coverage Generation这是功能安全的刚需。TESSY会解析函数内的所有条件判断如if((status MOTOR_RUNNING) (temp MAX_TEMP))自动生成最少用例数确保每个条件独立影响结果。一个含3个布尔条件的if语句TESSY会生成4个用例而非穷举的8个。实操技巧先用BVA生成基础用例再用MC/DC补全。生成后在“Test Case Editor”中批量编辑选中所有用例 → 右键 → “Set Expected Output”TESSY会根据当前桩配置自动计算预期输出值。比如当ADC_ReadChannel桩返回0x1FF对应温度85°C时MotorCtrl_CalculateDutyCycle应返回0x0000停机TESSY能自动推导并填入。3.5 第五步配置测试执行环境——让测试代码跑在“虚拟MCU”上点击“Test Execution” → “Configure Target”这是决定测试能否跑起来的关键。TESSY支持三种TargetHost PC (Windows/Linux)最快用于算法逻辑验证但无法测试硬件相关代码Target Hardware (Real MCU)最真实需连接J-Link调试器TESSY自动生成下载脚本QEMU Emulation折中方案用QEMU模拟ARM Cortex-M核能测试大部分寄存器操作速度比真机快5倍。我的推荐配置平衡速度与真实性开发阶段用QEMUTarget选择“ARM Cortex-M7 (QEMU)”认证阶段切到Target Hardware使用“J-Link GDB Server”配置要点在“Compiler Settings”中必须勾选“Use same compiler as original project”并指定IAR的iccarm.exe路径在“Linker Settings”中导入原工程的.icf链接脚本确保内存布局一致。注意QEMU模式下TESSY会自动处理“硬件寄存器模拟”。比如你的代码写了*(volatile uint32_t*)0x4000C000 0x1234;QEMU会把这个地址映射到一个虚拟内存区TESSY的桩能从中读取值。但QEMU不模拟外设时序所以涉及精确延时如__delay_cycles(100)的测试必须在真机上跑。3.6 第六步运行测试并分析结果——不只是“Pass/Fail”而是“为什么Fail”点击“Execute All Tests”TESSY开始编译、下载、运行。结果界面分三栏左栏“Test Cases”显示每个用例的状态GreenPass, RedFail, YellowNot Executed中栏“Execution Log”详细打印每一步执行过程包括桩调用记录、全局变量变化右栏“Coverage Report”实时显示语句覆盖率Statement、分支覆盖率Branch、MC/DC覆盖率。关键洞察点当出现Fail时不要只看“Expected: 0x0000, Actual: 0x0001”要深挖原因在“Execution Log”中搜索ADC_ReadChannel看桩是否返回了预期值展开失败用例的“Variable Trace”查看g_MotorState在函数执行前后的值是否被意外修改点击“Coverage Report”中的红色语句TESSY会高亮显示未执行的代码行并提示“此行未被任何测试用例触发”。我处理过一个经典FailMotorCtrl_CalculateDutyCycle在温度超限100°C时应返回0但测试总是返回非零值。Trace发现g_MotorState在测试前被初始化为MOTOR_STOPPED而函数内部有个状态机逻辑if(g_MotorState MOTOR_RUNNING) { ... } else { return 0; }。问题在于测试用例没设置g_MotorState为MOTOR_RUNNING导致直接走else分支——这暴露了需求文档的漏洞没说明“超温停机”前提必须是电机正在运行。3.7 第七步导出认证报告——不是截图而是生成TÜV认可的PDF包测试通过后点击“Reports” → “Generate Certification Report”。TESSY会生成一个.zip包解压后包含test_report.pdf含所有用例结果、覆盖率数据、执行环境摘要test_results.tss二进制结果文件含数字签名source_hash.txtUUT源码的SHA256哈希值tool_config.xmlTESSY版本、插件列表、配置参数。认证关键动作在PDF报告末尾有一个“Tool Qualification Certificate Reference”章节明确写着“Qualified per TÜV Certificate No. 01 100 2212425”。这个编号就是审计员查验的依据——他们用TÜV官网的验证工具输入这个编号和tss文件就能确认报告未被篡改。4. 单元测试与集成测试工程的分水岭何时该切怎么切4.1 单元测试工程聚焦“单个函数”的纯净验证单元测试Unit Test的黄金法则是隔离一切外部依赖只验证UUT自身的逻辑正确性。在TESSY中这意味着所有外部函数ADC_ReadChannel,PWM_SetDuty必须用桩Stub替代全局变量g_MotorState必须用TESSY生成的可控桩变量测试用例的输入必须覆盖所有边界值和MC/DC条件执行环境必须是Host PC或QEMU禁止真机避免硬件干扰。典型场景验证MotorCtrl_CalculateDutyCycle()的数学公式是否正确。输入targetRpm3000,actualRpm2900,temp85预期输出duty0x0A00。TESSY会生成一个独立的测试程序只包含UUT代码、桩代码、测试驱动编译后在PC上运行毫秒级出结果。实操心得单元测试的“快”不是目的“准”才是。我坚持一个原则每个单元测试用例的执行时间必须10ms。如果某个用例跑得慢说明它没做好隔离——比如还在调用真实的ADC驱动。这时要回溯第三步检查桩配置是否遗漏。4.2 集成测试工程验证“多个模块”的协同工作集成测试Integration Test的目标是在最小硬件环境中验证UUT与真实依赖模块的交互是否符合预期。在TESSY中这表现为选择性启用真实模块比如保留真实的ADC_Driver.c但用桩替代CAN_Transmit()使用Target Hardware执行必须连接真实MCU因为要测试硬件时序测试用例设计转向场景化不再是单个函数输入而是完整工作流如“上电→读ADC→计算占空比→输出PWM→读反馈电流”。创建集成测试工程的正确流程复制已有的单元测试工程File → Save As在“Test Interface”中将ADC_ReadChannel的桩配置从“Return Value”改为“Call Original Function”在“Target Configuration”中将Execution Target从QEMU切换到J-Link新增测试用例模拟真实场景如“ADC采样值从0x000上升到0x3FF观察PWM输出是否线性变化”。关键区别单元测试的覆盖率报告只统计UUT代码而集成测试的报告会包含被启用的真实模块如ADC_Driver.c的覆盖率。这意味着如果你启用了真实的ADC驱动TESSY会要求你为ADC_Driver_Init()也提供测试用例——否则覆盖率不达标。4.3 混合测试策略用TESSY的“测试套件Test Suite”实现无缝切换TESSY的Test Suite功能让你能在同一工程内管理单元和集成测试。操作如下创建两个Test SuiteUnit_Test_Suite和Integration_Test_Suite在Unit_Test_Suite中只添加UUT的单元测试用例在Integration_Test_Suite中添加启用了真实ADC的用例运行时右键Test Suite → “Execute”TESSY会自动应用对应的桩配置和Target设置。这样做的好处是代码基线统一无需维护两套工程审计时一份报告里同时包含单元和集成测试证据满足ASPICE对“验证层级”的要求。5. 常见问题与排查技巧实录那些TESSY文档里绝不会写的坑5.1 问题1TESSY报错“Function not found in source code”但函数明明存在现象在Define Test Interface时TESSY找不到MotorCtrl_CalculateDutyCycle即使它在motor_ctrl.c里定义了。排查路径检查函数声明TESSY只认头文件.h中的声明。如果motor_ctrl.h里只有extern uint16_t MotorCtrl_CalculateDutyCycle(...);但没加#ifndef __MOTOR_CTRL_H保护被多次include导致重复声明TESSY会跳过检查编译宏如果函数被#ifdef HW_VERSION_V2包裹而TESSY的“Preprocessor Definitions”里没定义HW_VERSION_V2函数就不会被解析检查函数属性如果函数有__attribute__((naked))或__ramfunc修饰TESSY默认不支持需在“Advanced Settings”中启用“Support for non-standard function attributes”。终极解法在TESSY的“Project Settings” → “C/C Parser”中勾选“Parse all preprocessor directives”并手动添加缺失的宏定义。5.2 问题2QEMU模式下测试通过但真机上Fail现象同一个用例在QEMU上Pass在J-Link真机上Fail且Fail位置随机。根因分析QEMU是理想化模拟不模拟MCU的物理特性。常见原因时钟精度差异QEMU的SysTick是理想周期真机受晶振偏差影响内存对齐问题QEMU允许未对齐访问ARM Cortex-M7真机访问未对齐地址会触发HardFault编译器优化差异IAR在-O2下可能把局部变量优化到寄存器而QEMU模拟的寄存器行为与真机不同。排查技巧在真机测试时开启TESSY的“Debug Mode”它会在失败时自动暂停并在GDB中显示Fault Handler的堆栈检查SCB-CFSR寄存器值如果是UNALIGNED位被置1说明有未对齐访问在TESSY的“Compiler Settings”中将优化等级临时改为-O0确认是否是优化问题。5.3 问题3MC/DC覆盖率始终卡在85%剩余15%怎么也打不满现象TESSY报告显示一个含5个布尔条件的if语句MC/DC覆盖率只有85%提示“Condition temp MAX_TEMP not independently tested”。真相MC/DC要求每个条件必须独立影响结果即改变该条件其他条件不变结果必须翻转。但你的代码可能是if((status MOTOR_RUNNING) (temp MAX_TEMP) (voltage MIN_VOLTAGE)) { // ... }如果status ! MOTOR_RUNNING整个表达式为假后面两个条件根本不会被求值短路求值所以TESSY无法让temp MAX_TEMP独立影响结果。解决方案重构代码把长条件拆成嵌套if确保每个条件都有机会被单独测试用TESSY的“Force Evaluation”功能在Test Interface配置中对temp MAX_TEMP勾选“Force evaluation”TESSY会生成额外代码绕过短路求值接受现实某些逻辑确实无法达到100% MC/DC这时需在认证文档中写明“未覆盖条件的原因及风险评估”这是ISO 26262允许的。5.4 问题4导出的PDF报告被审计员质疑“无法验证真伪”现象客户把TESSY生成的PDF交给TÜV对方说“这只是个普通PDF怎么证明没被PS过”正解审计员要的不是PDF而是tss文件和证书编号。正确交付方式将.zip包整体交付不要只发PDF在交付清单中注明“Tool Qualification Certificate No. 01 100 2212425验证方式访问https://www.tuv.com/certdb输入证书号查询”提供tss文件的SHA256哈希值让审计员用sha256sum test_results.tss自行比对。我的经验提前把TÜV的验证流程做成一页PPT附在交付包里。曾经有个项目客户拿着PPT直接通关了审计因为TÜV工程师看到我们连验证URL都准备好了立刻认定“这团队懂行”。6. TESSY测试工程的生命周期管理从“一次通过”到“持续演进”6.1 版本控制不是Git忽略.tss而是建立“测试基线”很多人把TESSY工程当普通代码.gitignore里加一行*.tss结果几个月后发现测试结果无法复现。正确做法是必须提交的文件.tes项目文件、.tcf配置文件、test_cases.xml用例定义、stubs/目录桩代码可以忽略的文件.tss结果文件、build/编译产物、reports/PDF报告关键动作每次代码变更后运行TESSY CLI --generate-test-cases --projectxxx.tes生成新的用例定义并提交到Git。这样任何人在任何时间checkout代码都能用TESSY一键重建完全相同的测试环境。6.2 与CI/CD流水线集成让TESSY成为“门禁”TESSY自带命令行工具TESSY CLI可无缝接入Jenkins或GitLab CI。一个典型的流水线步骤# 步骤1编译原工程生成.map文件 make -C ../original_project clean all # 步骤2用TESSY CLI生成测试代码 tessy-cli --projectmcu_test.tes --actiongenerate-code # 步骤3编译测试程序用原工程的Makefile make -C ./test_build clean all # 步骤4运行测试QEMU模式 tessy-cli --projectmcu_test.tes --actionexecute --targetqemu # 步骤5检查覆盖率阈值 if [ $(tessy-cli --projectmcu_test.tes --actionget-coverage --metricmc/dc) -lt 90 ]; then echo MC/DC coverage 90% - build failed! exit 1 fi实测效果某客户将TESSY集成到GitLab CI后平均每次PR的测试耗时从人工2小时缩短到自动7分钟且100%拦截了因重构引入的边界值缺陷。6.3 测试工程的“退休”时机当代码稳定后别让它成为负担一个健康的TESSY工程应该有明确的“退役”机制。我的建议是功能冻结期当模块进入量产且连续6个月无Bug修复可将测试工程标记为“Archived”归档内容打包project.tes、test_cases.xml、cert_report.pdf、tss文件存入长期存储如NAS退役条件只有当新需求导致UUT接口变更如增加参数、修改返回值才需“复活”测试工程重新定义接口并生成新用例。最后分享一个小技巧我在每个TESSY工程的根目录放一个README.md里面只写三行# TESSY Test Project for MotorCtrl Module Last Updated: 2024-03-15 (v2.3.1) Audit Evidence: TÜV Cert No. 01 100 2212425这三行就是未来任何审计员第一眼看到的“信任锚点”。