本文学习自《嵌入式软件动态运行时错误的检测》,主要是了解一下polyspace,工作过程中目前没有涉及到。
和桌面系统不同,对于嵌入式软件系统,软件测试主要是发现以下类型的错误:
运行时错误由ANSI C定义,是指那些能导致预定义之外的不正确结果或处理器停机的错误,其后果包括:
典型的运行时错误有以下几种:
传统的软件测试技术一般分为静态测试和动态测试,这两种测试方法在检测软件动态运行时错误方面有较多限制:
对于静态技术:
可见,静态测试技术可以检查软件代码的编程规范,分析程序的静态结构,对软件的质量进行度量。借助静态测试技术,可以使代码规范,结构清晰,但是不能有效地检查出只有动态运行才会出现的错误。
动态测试技术的一般步骤如下:
测试计划 -> 测试用例 -> 测试执行 -> 发现并提交BUG
这种方法一定程度上可以发现部分运行时错误,即测试用例所能覆盖到的错误,但是由于无法穷尽所有的输入,所以依赖于测试用例的测试最终只能保证在测试用例输入下不会导致运行时错误,无法保证其他输入情况下也可以正常工作。
既然上面的方法有缺陷,就需要借助新的方法与工具来实现运行时错误的检测 —— Polyspace
语义分析技术,依靠大量的数学定理提供的规则去分析软件的动态行为,这种方法没有使用简单的穷举法,但却可以在更普通的模式下表达程序的状态。
举个例子:
一个程序中使用了两个变量 x 和 y,对以下语句进行运行时错误检查:
x = x / (x-y);
问题来了:怎么建立一个合适的形状?
语义分析技术能够根据自己的规则,建立非常精确的形状,基于变量之间的关系,程序的控制结构(if-else、for、switch等),内部过程之间的关系(函数的调用),多任务分析等,进行运行时错误检测。
这里不涉及太多具体的规则(主要是不懂),经过一堆规则之后,上面的例子可以重新画出一个比较好的形状
以代码覆盖率测试为例,这里我们要求代码覆盖率达到100%
static void Recursion(int* depth)
/* if depth<0, recursion will lead to division by zero */
{
float advance;
*depth = *depth + 1;
advance = 1.0/(float)(*depth - 6); /* potential division by zero */
}
我们仅仅需要一个测试用例*depth = 10
即可使代码覆盖率达到100%,但是却没有发现最简单的Bug,例如*depth = 5 会导致除数为0这样的致命的错误。
覆盖率测试可能发现不了一些与数据相关的错误
覆盖率测试不可能查出程序中因遗漏路径而出错
当然,实际的代码可能比上述程序简单,假设GlobalFlag是一个被多任务使用的全局变量。
static void Recursion(int* depth)
/* if depth<0, recursion will lead to division by zero */
{
float advance;
*depth = *depth + 1;
advance = 1.0/(float)(*depth - GlobalFlag); /* potential division by zero */
}
*depth = 10这一个测试用例可能也会让代码覆盖达到100%,但这个100%能保证下面代码中的除数不为0吗?显然不能。
从另一个角度来看一下覆盖的概念
代码覆盖率不能仅考虑程序逻辑、语句分支的覆盖,还需要考虑数据输入的覆盖度
以int* depth 为例,取*depth = 10,代码覆盖率是达到了,但是对应的输入数据仅覆盖掉1/65535。
实际测试也不可能针对每个变量进行输入数据的全覆盖,所以传统的代码覆盖率检查很容易漏掉难以察觉的致命问题。
polyspace采用的是类似穷举的语义分析方法,可以将所有的可能情况都检查一遍,只要有一个输入存在问题,polyspace结果中就有相应的提示,这样的话,其实从代码覆盖率和数据输入的覆盖度两个方面都保证了100%的覆盖。