Testing vs. Model Checking
那么JPF所做的是测试我们的程序是否存在缺陷?不,它通常做得更多,至少当用作模型检查器时。那么,测试与模型检查有什么不同?
软件测试是一组经验技术,您可以在程序中执行大量输入,以确定其行为是否正确。这包括两个部分,它们涉及到正确的选择:test input and test oracle。
Testing 测试技术在我们如何选择输入时有所不同(random, "interesting" problem domain values like corner cases etc),以及对于被测系统的了解情况(system under test SUT),以及它的执行环境(黑/灰/白盒),这尤其影响我们如何定义和检查正确的行为。这需要大量的猜测,或者像Edsger dijkstra所说的:“程序测试最多只能显示错误的存在,但绝不能显示错误的缺失”,为此,我们通常通过执行“足够多”的测试用例来实现这一点。测试复杂系统通常可以说是大海捞针。如果你是一个优秀的测试人员,你做出了正确的猜测,并击中不可避免的缺陷。如果不是,不要担心-你的用户稍后会发现它。
Model Checking 模型检验作为一种非形式化方法,不依赖于猜测。至少理论上是这样的,如果有违反给定规范的行为,模型检查会找到的。模型检验被认为是一种严格的方法,它详尽地探索了SUT所有可能的行为。
为了说明这一点,请看例子Random number example 这显示了如果我们有一个使用随机值序列的程序,测试与模型检查是不同的:测试每次只处理一组值,而且我们对每一组没有控制权。而模型检查在检查所有数据组合或发现错误之前是不会停止的。
用Random number example这个例子,我们至少可以看到程序中的选择。假设这是一个并发编程示例concurrent programming example,您知道操作系统在线程之间切换的位置吗?我们所知道的是不同的调度序列可能导致不同的程序行为(如过有数据竞争的话)。但是我们在测试中很难强制改变调度的行为。有程序/测试规格的组合,是“不可测”。但是作为一台虚拟机,我们的软件模型检查器不会遭受相同的命运-它完全控制我们程序的所有线程,而且可以执行所有的调度组合。
这只是理论,实际上,“all possible”可能是一个相当大的数字-对于现有的计算资源或我们的耐心来说太大了。假设一个程序不同的调度序列有N个线程组成(P1...Pn),每个都有ni原子指令序列。
对于拥有2个原子片段的两个线程来说,每一个都给了我们6种不同的调度组合。对于有8个原子片段来说,这个数字是12870,有16个片段的是601080390-这就是为什么它容易产生状态爆炸。软件模型检查具有可测量性问题,甚至比硬件的模型检查更重要,因为程序通常有更多的状态。
There are several things we can do. First, we can optimize the model checker, which is simply an engineering feat. Next, we can find program states which are equivalent with respect to the properties we are checking, which can be done with various degrees of abstractions and value representations. Last, we can try to get to the defect first, before we run out of time or memory. But this is where the boundary between testing and model checking becomes blurred, as it involves things like heuristics about interesting input values or system behaviors, and these heuristics tend to drop reachable program states.
我们可以做这样几件事。首先,我们可以优化模型检查器,这将是一个工程壮举。接下来,我们可以找到与我们正在检查的属性等价的程序状态,可以用不同程度的抽象和价值表示来完成。最后,我们可以尝试在再耗尽时间或内存之前先找到缺陷。但是这就是测试和模型检查之间的界限变得模糊的地方,因为它涉及到诸如关于有趣的输入值或系统行为的启发式方法,而这些启发式方法倾向于删除可访问的程序状态。
JPF做了这些来限制状态爆炸的发生。而且大多数东西都是配置的,而不是硬连接的。所以你不依赖于内置的启发式方法。但不管我们缩小了多少状态空间,与常规测试相比,JPF仍然可以观察到更多关于程序执行的信息,而且它仍然知道执行的历史,以防我们发现一个缺陷--这只是第一部分。我们还需要理解它,使它最终可以被解决。