快速检查状态机
快速检查状态机
是基于的Haskell库在快速检查,用于测试有状态程序。图书馆不同于这个测试。快速检查。一元(Monadic)
方法因为它允许用户通过状态机指定正确性使用前置和后置条件的基于模型。状态机的优势方法有两个方面:1)指定程序的正确性变得越来越少adhoc,2)你可以免费测试比赛条件。
基于状态机的模型规范和基于属性的组合测试首先出现在Erlang的专有QuickCheck中。这个快速检查状态机
库可以被视为试图提供类似的Haskell的QuickCheck库的功能。
例子
作为第一个示例,让我们使用mutable实现和测试程序参考文献。我们的实施将使用IORef(IORef)
s、 但让我们从一个使用mutable表示程序可能执行的操作参考文献。我们可以创建、读取、写入和递增:
数据命令r=创建|读取(引用(不透明(IORef Int))r)|写入(引用(不透明(IORef Int))r)Int|增量(参考(不透明(IORef Int))r)数据响应r=已创建(引用(不透明(IORef Int))r)|读取值Int|书面|增量
当我们生成动作时,我们将无法创建任意的IORef(IORef)
s、 那是为什么所有使用IORef(IORef)
s被包裹参考_ r
,其中参数第页
将允许我们在生成时使用符号引用(当执行)。
为了能够显示反例,我们需要为我们的行动。IORef(IORef)
我们没有展示实例,这就是为什么我们要把它们包装起来不透明
; 它为没有show实例的类型提供show实例。
接下来,我们给出可变引用的实际实现。制造更有趣的是,我们通过一个可能的问题将语义参数化。
数据错误=无|逻辑|种族推导公式语义::错误->命令具体->IO(响应具体)语义错误cmd=的大小写cmd创建->创建<$>(reference.Opaque<$>newIORef 0)读取引用->ReadValue<$>readIORef(不透明引用)写入引用i->写入<$writeIORef(opaque ref)i'哪里--问题之一是一个错误,它将错误的值写入--参考。i'|bug==逻辑&&i`元素`[5..10]=i+1|否则=i增量ref->do--另一个问题是我们引入了一个可能的竞争条件--递增时。如果bug==比赛然后做i<-readIORef(不透明引用)threadDelay=<<随机RIO(0,5000)writeIORef(不透明引用)(i+1)其他的atomicModifyIORef'(不透明引用)(\i->(i+1,()))return增量
请注意,上面第页
实例化为混凝土
,本质上是身份类型,因此在编写语义时,我们可以访问realIORef(IORef)
第条。
现在我们有了一个实现,下一步是为要测试的实现。我们将在引用之间使用一个简单的映射和整数作为模型。
newtype模型r=模型[(引用(不透明(IORef Int))r,Int)]initModel::模型rinitModel=模型[]
动作的先决条件指定动作在什么上下文中定义明确。例如,我们总是可以创建一个新的可变引用,但是我们只能从已经创建的引用中读取。这个在生成程序(操作列表)时使用前置条件。
前提条件::模型符号->命令符号->逻辑precondition(Model m)cmd=的案例cmd创建->顶部读取ref->ref`member`map fst m写入ref _->ref `member`映射fst m增量ref->ref`member`map fst m
转换函数解释了操作如何更改模型。请注意过渡函数在第页
。原因是我们使用生成和收缩时的过渡函数(带有r~符号
)和执行时(使用r~混凝土
)命令序列。
转换::Eq1 r=>模型r->命令r->响应r->模型r过渡m@(模型模型)cmd resp=案例(cmd,resp)(Create,Created ref)->模型((ref,0):模型)(读取_,读取值_)->m(写入参考x,写入)->模型(更新参考x模型)(增量引用,增量)->案例查找引用模型仅i->模型(更新ref(succi)模型)更新:公式a=>a->b->[(a,b)]->[(b)]update ref i m=(ref,i):过滤器((/=ref)。fst)米
在我们执行操作并从实现(通过语义学
).
后置条件::模型具体->命令具体->响应具体->逻辑后置条件(模型m)cmd resp=的情况(cmd,resp)(创建,创建引用)->m'!参考==0 .// “创建”哪里模型m’=过渡(模型m)cmd resp(读取参考,读取值v)->v.==米!参考//“阅读”(写入_ref_x,写入)->顶部(增量_ref,增量)->顶部
接下来,我们必须解释如何生成和收缩动作。
生成器::模型符号->可能(Gen(命令符号))生成器(Model[])=Just(纯Create)发电机型号=仅$frequency[(1,纯创建),(4,读取<$>元素(映射fst模型),(4,写入<$>元素(映射fst模型)<*>任意),(4,增量<$>元素(域模型))]收缩器::模型符号->命令符号->[命令符号]shrinker _(写入ref i)=[写入ref i'|i'<-shink i]收缩机_ _=[]
停止生成新命令,例如,当模型达到终端或错误状态,让发电机
返回没有什么
.
最后,我们展示了如何模拟给定模型的响应。
模拟::模型符号->命令符号->GenSym(响应符号)mock(模型m)cmd=案例cmd创建->创建<$>genSym读取引用->读取值<$>纯(m!ref)写入_ _->纯写入增量_->纯增量
嘲弄
是一种黑客,使响应可能具有多个引用,以及一个可能有一天会让我们创建模拟API的实验。参见问题#236了解更多详细信息。
尽管在引用的问题中提到了什么嘲弄
功能将被使用在中推进模型时象征的
层,特别是在两个位置:
因此,嘲弄
必须提供将使模型向前发展的响应在票面价值实现。请注意,作为响应,可能会定义以下字段预计仅由使用后置条件
,这些可能装满了中的虚拟值嘲弄
作为后置条件
仅使用调用混凝土
实施的响应。
为了将代码放在一行中,我们将上面的所有代码都打包成一个记录。
sm::Bug->StateMachine模型命令IO响应sm bug=StateMachine initModel转换前提条件后置条件Nothing生成器收缩器(语义错误)模拟noCleanup
我们现在可以如下定义顺序属性。
prop_sequential::Bug->属性prop_sequential bug=forAllCommands sm'Nothing$\cmds->monadicIO$do(hist,_model,res)<-runCommands sm'命令prettyCommands sm'hist(checkCommandNames命令(res===确定))哪里sm'=sm错误
如果我们运行sequential属性而不向语义功能,即。快速检查(prop_sequential无)
,然后是属性传球。然而,如果我们引入了逻辑错误问题,那么它将以最小反例:
>快速检查(prop_sequential Logic)***失败!可伪造(12次测试和2次收缩后):命令{取消命令=[命令创建[Var 0],命令(写入(参考(符号(变量0)))5)[],命令(读取(参考(符号(变量0)))[]]}型号[]===创建===创建(参考(混凝土不透明))[0]型号[+_×_(参考不透明)0]==写入(参考(混凝土不透明))5==>写入[0]型号[_×_(参考不透明)-0+5]==读取(参考(混凝土不透明))==>读取值6[0]型号[_×_(参考不透明)5]PostconditionFailed“AnnotateC\”Read\“(谓词C(6:/=5))”/=确定
回想一下,错误问题导致写入值我`elem`[5..10]
到实际写入i+1
。还要注意模型的差异是如何显示的每个动作之间。
运行具有竞争条件问题的序列属性将无法发现比赛条件。
然而,如果我们定义一个并行属性如下。
prop_parallel::Bug->属性prop_parallel bug=forAllParallelCommands sm'无$\cmds->monadicIO$doprettyParallelCommands命令cmds=<<runParallelCommand sm'命令哪里sm'=sm错误
使用比赛条件问题运行它,然后我们将找到比赛条件:
>快速检查(prop_parallel Race)***失败!可伪造(26次测试和6次收缩后):并行命令{前缀=命令{unCommands=[Command Create[Var 0]]},后缀=[配对{项目1=命令{取消命令=[命令(增量(参考(符号(变量0)))[],命令(读取(参考(符号(变量0)))[]]},项目2=命令{取消命令=[命令(增量(参考(符号(变量0)))[]]}}]}——————————————————————————————————————————————————————————————————————————————│ [Var 0]←创建││ → 创建(参考(混凝土不透明)│└─────────────────────────────────────────────────────────────────────────────────────────────────┘┌──────────────────────────────────────────────┐ ││ []←增量(参考(混凝土不透明)│││ │ │ ┌──────────────────────────────────────────────┐│ │ │ │ []←增量(参考(混凝土不透明)││ │ │ │ → 增加了│ │ │ └──────────────────────────────────────────────┘│ → 增量││└──────────────────────────────────────────────┘ │┌──────────────────────────────────────────────┐ ││ []←读取(参考(混凝土不透明)│││ → ReadValue 1││└──────────────────────────────────────────────┘ │注释C“Read”(谓词C(1:/=2))
如上所述,首先创建了可变引用,然后在并行(并发)我们对所述引用进行两次增量,最后我们读取值1
而模型预期2
.
回想一下,递增是通过首先阅读参考和然后写入,如果两个这样的操作被交错,则其中一个写入可能会覆盖另一个——创建竞争条件。
我们将回到下面的示例,但如果您不耐烦,您可以找到完整的源代码代码在这里.
它是如何工作的
大致的想法是要求图书馆的用户提供:
- 动作的数据类型;
- 数据类型模型;
- 模型上动作的前后条件;
- 给定模型和动作的状态转移函数推进模型到下一个状态;
- 生成和收缩动作的方法;
- 用于执行动作的语义。
然后,该库返回一组组合符,让您定义一个顺序和并行属性。
Sequential属性
这个顺序属性检查模型是否与语义。这样做的方式是:
-
生成操作列表;
-
从初始模型开始,对每个动作执行以下操作:
- 检查前提条件是否成立;
- 如果是,则使用语义执行动作;
- 检查后置条件是否成立;
- 利用过渡函数对模型进行了改进。
-
如果出现问题,请缩小最初的操作列表并提交一个最小反例。
Parallel属性
这个平行属性检查语义的并行执行是否可以用顺序模型解释。这对于尝试查找比赛条件——这通常很难测试。它的工作原理是跟随:
-
生成操作列表并将其拆分为两个(或更多)部分:
- 将按顺序运行的第一部分,称为前缀(思考作为设置某些状态的初始化位);
- 第二部分(后缀)将被拆分为子列表并联运行(参见
parallelSafe(并行安全)
了解它是如何决定的一系列命令可以并行运行)。不止一个可以生成后缀,即第二步可以多次执行生成的列表中不属于前缀。
-
按照上述部分的顺序执行前缀(检查pre和岗位条件);
-
并行执行后缀,不检查前置/后置条件并收集每个人的调用和响应的跟踪(或历史记录)作用;
┌── 除了确保没有抛出异常之外,没有其他检查│╭─────────┴──────────╮┌─ [C] ————【F,G】◀─╮命令:[A,B]————————————————已执行`concurrently`└ [D,E]┘◀─╯╰─┬──╯ ▲ ▲│ ╰─────┬────╯│ └── 组不并行运行│ 即[C,D,e]将运行(和│ F或H之前│ 起动│└── 检查前置/后置条件和不变量按顺序执行
-
如果在执行命令时出现问题,请收缩生成的命令并提供最小反例;
-
否则,尝试寻找可能的动作顺序交错尊重后置条件的调用和响应。对于每个交错,这是通过推进混凝土
型号(起始于initialModel(初始模型)
)通过调用对的顺序命令混凝土
和返回的响应混凝土
在步骤3中发射,以及检查每对的后置条件。
-
如果没有发现可能的顺序交织,则收缩生成的命令并提供最小的反例。
最后两个步骤主要是试图找到一线性化的呼叫可能发生在单个线程上。
请注意,上面的步骤5在条件后检查和的转换模型混凝土
。尽管测试中的系统在并行方式,模型仍然可以设计为按顺序工作它会的不并行运行,只有当评估可能的交织。这特别意味着模型必须在用于并行测试之前,要确保顺序执行正确。
由于我们无法控制任务的实际调度命令(已经固定在具体的前缀和具体的后缀列表中)是默认情况下实际执行多次,即将执行步骤2至6同一测试用例需要多次调度事件不同的跑步记录会有所不同。可以通过引入随机变量进一步增加熵线程延迟
语义功能中的s。
更多示例
下面是一些更多的示例,让您开始:
-
水壶问题来自死硬3--这是一个简单的例子属于我们使用sequence属性来查找解决方案的规范(反例)动作片中的拼图。请注意,此示例没有有意义的语义,我们只是模型检查。这可能有助于将解决方案与刺猬解决方案和这个TLA公司+解决方案;
-
河内塔谜题——这个例子使用以与Die Hard公司例子找到经典的解决方案河内之塔拼图;
-
可变参考例子--这是一个更大的示例,它显示了sequential属性如何查找正常的bug,以及parallel属性如何查找争用条件;
-
循环缓冲器例子--另一个示例显示sequential属性如何查找help find不同种类的错误。这个例子是从论文中借来的测试硬物和保持理智[PDF格式,视频]. 为了更直接翻译自论文,见下文变体使用外国金融机构的机构;
-
联合发现例子--union-find算法的必要实现。可能是这样将解决方案与论文中的解决方案进行比较很有用测试带QuickCheck的单数代码[秒],其中测试。快速检查。一元(Monadic)
模块基于;
-
门票分配器例子--一个简单的示例,其中再次使用parallel属性来查找比赛条件。本例中的语义使用一个简单的数据库文件需要进行设置和清理。此示例也出现在使用QuickCheck测试数据库的比赛条件和测试硬物和停留Sane公司[PDF格式,视频]论文;
-
CRUD Web服务器,其中create返回唯一的入侵检测系统例子--在web服务器上的postgres数据库中创建、读取、更新和删除用户使用编写的API使用仆人.创建用户将返回一个唯一的id,后续的读取、更新和删除需要该id使用。在这个示例中,与上一个示例不同,服务器设置为每处房产拆一次,而不是生成程序;
-
书店例子--另一个数据库应用程序,它使用简单的SQL查询来管理书店。它基于案例研究来自Fred Hebert的在线版本的ErlangPropEr测试书;
-
进程注册表例子--Erlang QuickCheck论文中经常提到的一个例子。这个例子显示了如何标记包含需求的规范然后生成涵盖每个测试用例的(最小)示例要求,如您的需求测试得如何?[PDF格式]和通过好的例子理解形式规范[PDF格式,视频]论文。
示例中的所有属性都可以在规格
模块位于测试
目录。
为了更好地理解示例,可能有助于git克隆
这回购,光盘
进去,开火电缆应答试验
,加载不同的示例,例如:l测试/CrudWebserverDb.hs
,并运行不同的属性以交互方式。
真实世界示例
更多来自“现实世界”的例子:
-
IOHK在几个方面使用状态机模型地点.例如在这里是对模拟文件系统的测试,他们反过来使用该系统来模拟文件测试区块链数据库时出现系统错误。以下博客邮递描述了他们的更详细的测试;
-
导线使用状态机模型测试推送通知系统中运行线程的下限;
-
Raft一致性算法的伴随(现已放弃?)实现,包含状态机测验结合故障注入(节点和网络故障)。
使用快速检查状态机
作为依赖项
为了带来快速检查状态机
在代码中,将其添加到取决于构建
cabal文件中的节:
测试套件。。。取决于构建:,快速检查状态-机器
这个依赖项可以开箱即用,但是,它带有一个的旧版本树差异
供应商内部。
对于快速检查状态机
要保持BSD风格的许可,它不能取决于树差异
将其许可证更改为GPL公司
,因此,解决方案是提供一个旧的BSD风格的许可版本使用此供应商版本,无论代码是否正在测试代码与否,可以保持BSD风格的许可。
如果你想利用上游树差异
包,您可以依赖在稍微不同的库名称上:
测试套件。。。取决于构建:,快速检查状态-机器:无支持-自由
为了正确编译,您必须提供自己的机器实施CanDiff(CanDiff)
使用上游类树差异
定义。你必须实现与这模块(正在进行正是这个,但有一个旧的树差异
版本)。
如何做出贡献
这个快速检查状态机
这个图书馆仍然很有实验性。
我们希望鼓励用户试用它,并加入关于如何我们可以在问题跟踪器上改进它!
另请参见
-
QuickCheck错误跟踪问题--其中关于如何将基于状态机的测试添加到QuickCheck已启动;
-
2019年约翰·休斯中部研究生院课程论财产基础测试,它涵盖了状态机建模和测试的基础知识。它还包含状态机测试库的最小实现建立在Haskell的QuickCheck之上;
-
使用QuickCheck和查找Erlang中的竞争条件脉冲[PDF格式,视频]--这是第一篇描述Erlang的QuickCheck如何工作(包括并行测试);
-
线性化:并发对象的正确条件[PDF格式、TLA+正式化],这是一个经典介绍了并行性质的主要技术;
-
Aphyr关于杰普森,其中还使用线性化技术,并在许多分布式系统:
-
使用状态机对程序的属性进行建模和验证是正如几本关于这一主题的书所证明的那样,这本书已经很成熟了:
这些书包含如何使用状态机对系统建模的一般建议,因此与我们相关。关于为什么状态机对于建模很重要,请参见:
-
其他类似库:
-
Erlang快速检查,等效电路,第一个基于属性的测试库支持状态机(封闭源);
-
Erlang库PropEr公司是等效电路-受到启发,开源,并支持国家机器测试;
-
哈斯克尔家族图书馆刺猬,此外支持基于状态机的测试;
-
Scala检查,同样支持国家机器基于测试(没有平行属性);
-
蟒蛇图书馆假设,此外支持状态机基于测试(没有并行属性)。
历史和现状
这个图书馆最初是在我工作的时候开发的ATS高级Telematic Systems股份有限公司2017年至2018年。2018年HERE欧洲公司获得自动转换开关并接管了高级远程信息处理GitHub组织。我左边酒店雇员和饭馆雇员2019年和2021年,他们将旧的快速检查状态机
repo将其设置为只读,这就是创建此fork的时间。
我不再使用快速检查状态机
每天都有,没有计划做出任何重大改变。这就是说,我认为该库的功能相当强大完整稳定,我很乐意做小保养工作。我也很高兴帮助和指导任何愿意承担更积极发展角色的人。
许可证
BSD样式(请参阅文件LICENSE)。