在嵌入式软件单元测试中,TESSY桩函数的返回值怎么模拟、调用顺序又该怎么校验,是实际项目里常常碰到的两个问题。很多被测函数并不是完全独立运行的,它在执行时总会去调用一些外部的函数,比如底层驱动、通信接口、诊断接口或者存储接口等等,如果让这些外部函数直接参与测试,结果就很容易被硬件环境、通信状态甚至其他模块干扰,所以需要先用桩函数把外部依赖替换掉。桩函数的重点,并不是随便写上一个空函数就算完事,而是要能模拟返回值、输出参数、调用次数还有调用的先后次序,这样得出来的测试结果才有真正的价值。
一、TESSY桩函数怎么模拟返回值
在TESSY里面,桩函数主要就是用来顶替被测单元所调用的那些外部函数。动手去模拟返回值之前,先要弄明白被替代的函数在整条业务逻辑里到底扮演什么角色,它究竟是只返回一个状态码,还是返回一个计算好的数值,或者通过指针类型的参数把结果带出来。函数类型不一样,模拟的办法自然也不完全相同。
1、先确认外部函数的作用
开始设置桩函数之前,一定要看清楚被测函数到底为什么去调用这个外部函数。比如读取传感器数值、获取通信状态、访问NVM里保存的数据,或者查询诊断的状态,这些调用全都会直接左右被测函数内部的分支走向。只有把外部函数扮演的角色真正吃透了,后面模拟返回值才不会变成随手胡乱塞一个数。
2、用固定返回值覆盖普通分支
进入【Test Case】中为桩函数设置固定返回值,让外部函数在本条用例中返回指定结果。
这种方式对付一些比较简单的场景是很合适的,比如直接让外部函数返回E_OK、E_NOT_OK、TRUE、FALSE,或者一个固定的状态码。设计用例的时候,就可以分别去模拟成功、失败、超时、无效数据这些情况,再回过头来看被测函数是不是踏进了预想的那条分支当中。
3、用不同用例模拟不同状态
同一个桩函数,不要翻来覆去就只给它准备同一种返回值。比如读接口仅仅让它跑通一次正常值,充其量只能说明正常路径走得通;要是它返回了错误码、边界值,或者根本不合法的状态,被测函数还能不能稳稳处理好,那才是单元测试更需要操心的地方。像状态机、诊断逻辑、保护逻辑这类代码,通常得多备几条用例,把关键的那些状态挨个覆盖到。
4、注意输出参数的模拟
还要留心一类函数,它表面上好像只是返回一个值,真正的业务数据却悄悄藏在指针参数里面。比如函数顺顺当当返回了E_OK,同时又通过参数写入了电压值、温度值或者诊断结果;碰到这种情况,光改动返回值远远不够,还得在桩函数里把那些通过指针传递的输出参数也一并模拟出来,否则被测函数拿不到真实的业务数据,测试结论就容易显得虚浮。
二、TESSY桩函数调用顺序怎么校验
校验调用顺序,主要就是为了确认被测函数是不是严格遵照设计规定好的先后次序去调用外部接口。比如应当先做初始化再读取数据,先检查状态再执行输出,先把驱动关掉再上报错误。一旦这些先后关系弄反了,单看一次运行的结果也许还觉得挺正常,可放到真实系统里就很容易惹出时序上的毛病或者状态异常。
1、先明确期望调用链路
调用顺序不能凭感觉去猜测,得先根据软件详细设计或者代码本身的逻辑,把期望跑出来的那条调用链路仔仔细细地理清楚。例如函数执行起来后,应该最先去调状态检查的接口,接着去调数据读取的接口,最后才轮到输出控制的接口。只有期望的先后顺序明明白白摆在那里,后面才好去判断实际调用到底是对是错。
2、用调用计数记录执行过程
在桩函数中设置【Call Counter】,记录每个外部函数被调用的次数和先后位置。
这种方法非常适合检查某个接口到底有没有被调用过、一共被调了几次,以及在走岔了的分支下是不是被跳过去了。比如错误返回以后,按规矩就不该继续调用输出接口;初始化如果已经失败,也不该再硬着头皮去执行写入动作,这些全都能通过调用计数来核实。
3、用序号变量校验先后关系
遇到对顺序要求特别苛刻的场景,可以在好几个桩函数里面共同维护一个顺序变量。第一个桩函数被调用时就给它记下序号1,等到第二个桩函数被调用时,先去检查前一个序号是不是已经出现过了,要是对不上,就让测试直接判失败,或者留一个错误标志下来。这种抓法比只盯着调用次数要准确得多,因为次数对了,不代表里面的先后关系就一定对。
4、关注异常分支下的调用顺序
许多隐藏的问题并不是窝在正常路径上,而是躲在异常路径里面。比如读取失败了还会不会硬要继续执行控制输出,诊断出异常后是不是先上报再降级,通信超时以后是不是一个劲儿地反复调同一个接口。所以在验证调用顺序时,不能只给正常路径一路开绿灯,也要把失败、超时、边界这些状态底下的调用次序一块儿验证到。
三、TESSY桩函数测试证据怎么整理
桩函数相关的测试证据,不要到了最后只丢出一张“测试通过”的截图就认为万事大吉。在评审的时候,真正需要往下追的,是桩函数究竟模拟了什么样的条件,被测函数有没有按照期望给出响应,调用次数和调用顺序有没有被切实地验证过。证据越是能紧紧贴住测试的初始目的,后面就越好一五一十地解释清楚。
1、保留桩函数配置说明
动手整理证据时,先得交代明白,到底有哪些外部函数被我们用桩函数换掉了,是因为什么原因非要替换不可,以及每一个桩函数到底模拟了怎样的返回值或者输出参数。对于跟安全挂上钩的逻辑,还要额外讲清楚,眼前的这套模拟条件对应的是正常场景、异常场景还是边界场景,不要含糊带过。
2、保存用例和执行结果
在【测试报告】中保留用例输入、桩函数返回值、预期结果、实际结果、调用次数和执行状态。
要是某条用例本来就是冲着校验调用顺序去的,那就应该能从报告里看到实际跑下来的调用链路到底符不符合预期。不要偷懒只写一个“Pass”就草草了事,最好把调用的记录、判断条件或者相关的日志都保留下来,这样证据的整条链子才会显得更瓷实。
3、把问题和复测记录闭环
假如在测试中发现了桩函数返回异常值后,被测函数压根没有走进预想的那条分支,或者调用顺序跟设计对不上,这时候就要老老实实把缺陷记下来,把修改的内容记下来,再把复测的结果原原本本地记录清楚,一样也别漏掉。桩函数测试的价值,不光是为了证明眼下代码能顺利跑通,更要紧的是能借着外部依赖的变化,把那些潜藏着说不定哪天就冒出来的问题,提前给拽到明面上来。
总结
总体上讲,TESSY桩函数的返回值模拟和调用顺序校验,核心思路就是先把外部依赖稳稳控制住,再回过头去打量被测函数在接收到不同返回值、不同输出参数,以及面对各种调用时序时,它的表现有没有还趴在设计的谱子上。返回值模拟需要把正常的、异常的还有边界的状况全都拢进去,调用顺序校验则要紧盯调用次数、先后次序和那些容易被漏掉的异常路径。到最后,把桩函数是怎么配置的、测试用例是怎么执行的、跑出了什么结果,以及缺陷和复测记录都整理齐全,TESSY的单元测试证据才算得上比较周全。