快速检查状态机
快速检查状态机
是基于的Haskell库在快速检查,用于测试有状态程序。图书馆不同于这个测试。快速检查。一元(Monadic)
方法因为它允许用户通过状态机指定正确性使用前置和后置条件的基于模型。状态机的优势方法有两个方面:1)指定程序的正确性变得越来越少adhoc,2)你可以免费测试比赛条件。
基于状态机的模型规范和基于属性的组合测试首先出现在Erlang的专有QuickCheck中。这个快速检查状态机
库可以被视为试图提供类似的Haskell的QuickCheck库的功能。
例子
作为第一个示例,让我们使用mutable实现和测试程序参考文献。我们的实施将使用IORef(IORef)
s、 但让我们从一个使用mutable表示程序可能执行的操作参考文献。我们的可变引用可以创建、读取、写入和递增:
数据操作(v::*->*)::*->*其中新::Action v(不透明(IORef Int))阅读::参考v(不透明(IORef Int))->操作v Int写入::引用v(不透明(IORef Int))->Int->操作v()Inc::参考v(不透明(IORef Int))->操作v()
当我们生成动作时,我们将无法创建任意的IORef(IORef)
s、 那是为什么所有使用IORefs(IORefs)
被包裹着参考v
,其中参数v(v)
将允许我们在生成时使用符号引用(当执行)。
为了能够显示反例,我们需要为我们的行动。IORef(IORef)
我们没有展示实例,这就是为什么我们要把它们包装起来不透明
; 它为没有show实例的类型提供show实例。
接下来,我们给出可变引用的实际实现。制作更有趣的是,我们通过一个可能的问题将语义参数化。
数据问题=无|Bug|RaceCondition推导公式语义::问题->操作具体响应->IO响应semantics_New=不透明<$>newIORef 0semantics_(Read-ref)=readIORef(不透明引用)语义prb(Write-ref i)=writeIORef(opaque ref)i'哪里--问题之一是一个错误,它将错误的值写入--参考。i'|i`elem`[5..10]=如果prb==错误,则i+1其他i|否则=i语义prb(Inc-ref)=--另一个问题是我们引入了一个可能的竞争条件--递增时。如果prb==种族条件然后做i<-readIORef(不透明引用)threadDelay=<<随机RIO(0,5000)writeIORef(不透明引用)(i+1)其他的atomicModifyIORef'(不透明引用)(\i->(i+1,()))
请注意,上面v(v)
实例化为混凝土
,本质上是标识类型,所以在编写语义时,我们可以访问realIORef(IORef)
第条。
现在我们有了一个实现,下一步是为要测试的实现。我们将在引用之间使用一个简单的映射和整数作为模型。
newtype Model v=模型[(参考v(不透明(IORef Int)),Int)]initModel::模型vinitModel=模型[]
动作的先决条件指定动作在什么上下文中定义明确。例如,我们总是可以创建一个新的可变引用,但是我们只能从已经创建的引用中读取。这个在生成程序(操作列表)时使用前置条件。
前提条件::模型符号->动作符号响应->布尔prediction _ New=真precondition(Model m)(Read-ref)=ref`elem`map fst mprecondition(Model m)(Write ref_)=ref`elem`映射fst mprecondition(Model m)(Inc-ref)=ref`elem`映射fst m
转换函数解释了操作如何更改模型。请注意过渡函数在v(v)
。原因是我们使用生成和执行时的转换函数。
转换::模型v->Action v resp->v resp->模型v过渡(模型m)新参考=模型(m++[(参考参考,0)])过渡m(读取_)_=m过渡(模型m)(写入参考i)_=模型(更新参考i m)过渡(模型m)(Inc-ref)_=模型(更新ref(旧+1)m)哪里仅旧=查找参考m更新:公式a=>a->b->[(a,b)]->[(b)]update ref i m=(ref,i):过滤器((/=ref)。fst)米
在执行操作并访问结果。
后置条件::模型混凝土->行动混凝土resp->resp->属性后置条件_新_=属性True后置条件(模型m)(读取参考)resp=查找参考m===仅响应后置条件_(写入_ _)_=属性True后置条件_(Inc_)_=属性True
最后,我们必须解释如何生成和收缩动作。
生成器::模型符号->生成(非类型操作)发电机(型号m)|null m=纯(无类型新)|否则=频率[(1,纯(无类型新)),(8,未键入。读取<$>元素(映射fst m)),(8,非类型化<$>(写入<$>元素(映射fst m)<*>任意)),(8,Untyped.Inc<$>元素(映射fst m))]收缩机::动作v响应->[动作v响应]shrinker(Write ref i)=[写入ref i“|i”<-shink i]收缩机=[]
为了将代码放在一行中,我们将上面的所有代码都打包成一个记录。
sm::问题->状态机模型操作IOsm prb=状态机生成器收缩预处理转换后置条件initModel(语义prb)id
我们现在可以如下定义顺序属性。
prop_references::问题->属性prop_references prb=单子序列(sm prb)$\prog->do(历史记录、模型、属性)<-runProgram(sm prb)progprettyProgram项目历史模型$checkActionNames程序编号OfConstructors属性哪里numberOfConstructors=4个
如果我们运行sequential属性而不向语义功能,即。快速检查(prop_references无)
,然后是属性传球。然而,如果我们引入了bug问题,那么它将因最小反例:
>快速检查(prop_references Bug)***失败!可伪造(经过16次测试和4次收缩):[新建(Var 0)、写入(Var o)5(Var 2)、读取(Var O)(Var 3)]只有5/=只有6
回想一下,错误问题导致写入值我`elem`[5..10]
到实际写入i+1(输入+1)
.
运行具有竞争条件问题的序列属性将无法发现比赛条件。
然而,如果我们定义一个并行属性如下。
prop_referencesParallel::问题->属性prop_referencesPralle prb=monadicParallel(sm prb)$\prog->prettyPparallelProgram程序=<<运行并行程序(sm prb)程序
使用比赛条件问题运行它,然后我们将找到比赛条件:
>快速检查(prop_referencesParallel RaceCondition)***失败!(经过8次试验和6次收缩后):无法线性化:┌────────────────────────────────┐│ 变量0←新││ → 不透明│└────────────────────────────────┘┌─────────────┐ ││ 增量(变量0)│││ │ │ ┌──────────────┐│ │ │ │ 增量(变量0)││ → () │ │ │ │└─────────────┘ │ │ ││ │ → () ││ └──────────────┘│ ┌──────────────┐│ │ 读取(变量0)│ │ → 1 ││ └──────────────┘只有2/=只有1
如上所述,首先创建了可变引用,然后在并行(并发)我们对所述引用进行两次增量,最后我们读取值1
而模型预期2
.
回想一下,递增是通过首先阅读参考和然后写入,如果两个这样的操作被交错,则其中一个写入最终可能会覆盖另一个——创建竞争条件。
我们将回到下面的例子,但如果你不耐烦,你可以找到完整的源代码代码在这里.
它是如何工作的
粗略的想法是要求图书馆用户提供:
- 动作的数据类型;
- 数据类型模型;
- 模型上动作的前后条件;
- 给定模型和动作的状态转移函数推进模型到下一个状态;
- 生成和收缩动作的方法;
- 执行操作的语义。
然后,该库返回一组组合符,让您定义一个顺序和并行属性。
Sequential属性
这个顺序属性检查模型是否与语义。这样做的方式是:
-
生成操作列表;
-
从初始模型开始,对每个动作执行以下操作:
- 检查前提条件是否成立;
- 如果是,使用语义执行操作;
- 检查后置条件是否成立;
- 利用过渡函数对模型进行了改进。
-
如果出现问题,请缩小最初的操作列表并提交一个最小反例。
Parallel属性
这个平行性质检查语义的并行执行是否可以用顺序模型解释。这对于尝试查找比赛条件——这通常很难测试。它的工作原理是跟随:
-
生成一个操作列表,该列表将作为并行程序(将其视为设置的初始化位某些州);
-
生成两个动作列表,作为并行后缀;
-
按顺序执行前缀;
-
并行执行后缀并收集每个动作的调用和响应;
-
尝试找到动作调用的可能顺序交错尊重后条件的回应。
最后一步主要是试图找到一线性化的呼叫可能发生在单个线程上。
更多示例
下面是一些更多的示例,让您开始:
-
水壶问题来自死硬2--这是一个简单的例子属于我们使用序列属性来寻找解决方案的规范(反例)动作片中的拼图。请注意,此示例没有有意义的语义,我们只是模型检查。这可能有助于将解决方案与刺猬解决方案和这个TLA公司+解决方案;
-
这个联合发现例子--sequential属性的另一种用法,这次使用了有用的语义(union-find算法的强制实现)。它可能很有用将解决方案与论文中的解决方案进行比较测试一元代码快速检查[PS(聚苯乙烯)],哪个是测试。快速检查。一元(Monadic)
模块基于;
-
可变参考例子--这是一个更大的例子,展示了sequence属性如何查找普通的bug,以及parallel属性如何查找竞争条件。几个元属性,例如检查反例是否为最小值,在分离模块;
-
循环缓冲器例子--另一个示例显示sequential属性如何查找help find不同种类的错误。这个例子是从报纸上借来的测试硬物和保持理智[PDF格式,视频];
-
门票分配器例子--一个简单的例子,其中再次使用parallel属性来查找比赛条件。本例中的语义使用一个简单的数据库文件需要进行设置和清理。此示例也出现在使用QuickCheck测试数据库的比赛条件和测试硬物和停留Sane公司[PDF格式,视频]论文;
-
积垢Web服务器例子--使用编写的API在Web服务器上创建、读取、更新和删除文件使用仆人. The规范使用两个固定的文件名进行测试,而web服务器是为每个生成的程序设置和删除;
-
CRUD Web服务器,其中create返回唯一的身份证例子--在web服务器上的sqlite数据库中创建、读取、更新和删除用户使用编写的API使用仆人.创建用户将返回一个唯一的id,后续的读取、更新和删除需要该id使用。在这个示例中,与上一个示例不同,服务器设置为为每个属性删除一次,而不是生成程序。
所有示例都有关联的规格
模块位于这个示例/测试
目录。它们利用了示例中的属性,并作为一部分进行了测试属于特拉维斯CI.
为了更好地理解示例,可能有助于git克隆
这回购,光盘
进入示例/
目录并启动堆栈ghci
并运行不同属性交互。
如何做出贡献
这个快速检查状态机
这个图书馆仍处于实验阶段。
我们希望鼓励用户试用它,并加入关于如何我们可以在问题跟踪器上改进它!
另请参见
-
快速检查错误跟踪问题--其中关于如何将基于状态机的测试添加到QuickCheck已启动;
-
使用QuickCheck和查找Erlang中的竞争条件脉冲[PDF格式,视频]——这是第一篇描述Erlang的QuickCheck如何工作(包括并行测试);
-
线性化:并发的一个正确条件物体[PDF格式],这个是一篇描述并行的主要技术的经典论文属性;
-
Aphyr关于杰普森,其中还使用线性化技术,并在许多分布式系统:
-
使用状态机对程序的属性进行建模和验证是正如几本关于这一主题的书所证明的那样,这本书已经很成熟了:
这些书包含如何使用状态机对系统建模的一般建议,因此与我们相关。关于为什么状态机是对于建模很重要,请参见:
-
其他类似库:
-
Erlang快速检查,等效电路,第一个基于属性的测试库支持状态机(封闭源);
-
Erlang库PropEr公司是等效电路-受到启发,开源,并支持国家机器测试;
-
哈斯克尔家族图书馆刺猬,还支持基于状态机的测试;
-
Scala检查同样支持国家机器基于测试(否平行属性);
-
蟒蛇图书馆假设,还支持状态机基于测试(否并行属性)。
许可证
BSD样式(请参阅文件LICENSE)。