测试大型状态机有哪些策略?

我inheritance了一个庞大且相当复杂的状态机。 它有31种可能的状态,都是真正需要的(大业务流程)。 它有以下输入:

  • 枚举:当前状态(所以0 – > 30)
  • 枚举:来源(目前只有2个条目)
  • 布尔值:请求
  • 布尔值:类型
  • 枚举:状态(3个州)
  • 枚举:处理(3个州)
  • 布尔值:已完成

将其分解为单独的状态机似乎不可行,因为每个状态都是不同的。 我为最常见的输入编写了测试,每个输入一个测试,所有输入都是常量,除了State。

[Subject("Application Process States")] public class When_state_is_meeting2Requested : AppProcessBase { Establish context = () => { //Setup.... }; Because of = () => process.Load(jas, vac); It Current_node_should_be_meeting2Requested = () => process.CurrentNode.ShouldBeOfType(); It Can_move_to_clientDeclined = () => Check(process, process.clientDeclined); It Can_move_to_meeting1Arranged = () => Check(process, process.meeting1Arranged); It Can_move_to_meeting2Arranged = () => Check(process, process.meeting2Arranged); It Can_move_to_Reject = () => Check(process, process.Reject); It Cannot_move_to_any_other_state = () => AllOthersFalse(process); } 

没有人完全确定每个状态和输入集的输出应该是什么。 我已经开始为它编写测试了。 但是,我需要写一些像4320测试(30 * 2 * 2 * 2 * 3 * 3 * 2)的东西。

您对测试状态机有什么建议?


编辑:我正在玩所有的建议,并在找到效果最好的时候标记答案。

我看到了问题,但我肯定会尝试将逻辑分开。

我眼中最大的问题是:

  • 它有31种可能的状态。
  • 它有以下输入:
    • 枚举:当前状态(所以0 – > 30)
    • 枚举:来源(目前只有2个条目)
    • 布尔值:请求
    • 布尔值:类型
    • 枚举:状态(3个州)
    • 枚举:处理(3个州)
    • 布尔值:已完成

有太多的事情发生了。 输入使代码难以测试。 你已经说过把它分成更容易管理的区域会很痛苦,但是在开始时测试这么多逻辑也同样有点痛苦。 在您的情况下,每个unit testing覆盖太多的地面。

我问过关于测试大型方法的这个问题本质上是类似的,我发现我的单位太大了。 你最终还是会进行很多测试,但它们会更小,更易于管理,覆盖范围更小。 这只能是一件好事。

测试旧版代码

看看Pex 。 您声称inheritance了此代码,因此这实际上不是测试驱动开发。 您只是希望unit testing涵盖每个方面。 这是一件好事,因为任何进一步的工作都将得到validation。 我个人还没有正确使用Pex,不过我看到的video让我惊叹不已。 本质上,它将基于输入生成unit testing,在这种情况下,输入将是有限状态机本身。 它将生成您不会想到的测试用例。 虽然这不是TDD,但在这种情况下,测试遗留代码,它应该是理想的。

获得测试覆盖后,您可以开始重构,或者添加具有良好测试覆盖率安全性的新function,以确保您不会破坏任何现有function。

全对测试

要限制要测试的组合数量并合理保证您已经涵盖了最重要的组合,您应该看一下全对测试。

所有对测试背后的原因是:程序中最简单的错误通常由单个输入参数触发。 下一个最简单的错误类别包括依赖于参数对之间的相互作用的那些错误,这些参数可以通过所有对测试来捕获.1涉及三个或更多参数之间的交互的错误逐渐变得不常见2,同时逐渐变得更加昂贵通过详尽的测试找到,其中包括对所有可能输入的详尽测试。

另外,请看一下之前的答案 (无耻插件)以获取更多信息,并链接到all-pair和pict作为工具。

示例Pict模型文件

给定模型生成93个测试用例,涵盖所有输入参数对。

 # # This is a PICT model for testing a complex state machine at work # CurrentState :0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30 Source :1,2 Request :True, False Type :True, False Status :State1, State2, State3 Handling :State1, State2, State3 Completed :True,False # # One can add constraints to the model to exclude impossible # combinations if needed. # # For example: # IF [Completed]="True" THEN CurrentState>15; # # # This is the PICT output of "pict ComplexStateMachine.pict /s /r1" # # Combinations: 515 # Generated tests: 93 

我想不出任何简单的方法来测试这样的FSM,而不是真正迂腐和使用证据,使用机器学习技术,或蛮力。

蛮力:写一些能以某种声明方式生成所有4320个测试用例的内容,其中大部分数据都是错误的。 我建议将其放在CSV文件中,然后使用类似NUnits参数测试的方法来加载所有测试用例。 现在,大多数这些测试用例都会失败,因此您必须手动更新声明性文件才能正确,并且只需随机修改测试用例样本即可。

机器学习技术:您可以使用一些Vector机器或MDA算法/启发式方法来尝试从您上面提到的样本中学习,并将您的ML程序教给您的FSM。 然后在所有4320输入上运行算法,看看两者不同意的地方。

您认为需要多少次测试来“完全”测试函数sum(int a,int b)? 在c#中它会像18446744056529682436那样测试……比你的情况要糟糕得多。

我建议遵循:

  1. 测试大多数情况,边界条件。
  2. 分别测试SUT的一些关键部分。
  3. 在QA或生产中发现错误时添加测试用例。

在这种特殊情况下,最好的方法是测试系统如何从一种状态切换到另一种状态。 创建DSL以测试状态机并使用它来实现最常见的用例。 例如:

 Start .UploadDocument("name1") .SendDocumentOnReviewTo("user1") .Review() .RejectWithComment("Not enough details") .AssertIsCompleted() 

创建流程简单测试的示例如下: http : //slmoloch.blogspot.com/2009/12/design-of-selenium-tests-for-aspnet_09.html

使用SpecExplorer或NModel 。

我为一件医疗设备构建了一台有限状态机。 FSM可以通过我定义的XML格式进行配置。

要定义状态机,必须依靠使用状态图的数字电路设计经验,

你必须使用我称之为收费公路过渡地图。 在美国东海岸,大多数高速公路都被称为转弯。 收费公路当局发出收费公路收费定价地图。 如果收费部分有50个出口,定价图将有一个50个x 50个表格,将出口列为行和列的详尽列表。 要查找进入退出20和退出退出30的通行费,您只需查找第20行和第30列的交叉点。

对于30个状态的状态机,收费公交转换图将是30 x 30矩阵,列出所有30种可能状态的行和列。 让我们将行确定为CURRENT状态,将列确定为NEXT状态。

每个交叉单元格将列出从CURRENT状态(行)转换到NEXT状态(col)的“价格”。 但是,单元格不是单个$值,而是引用Inputs表中的一行,我们可以将其称为转换ID。

在我开发的医疗设备FSM中,有一些输入是String,enums,int等.Inputs表按列列出了这些输入激励。

要构造Inputs表,您可以编写一个简单的例程来列出所有可能的输入组合。 但桌子会很大。 在您的情况下,该表将有4320行,因此4320转换ID。 但它不是一个单调乏味的表,因为您以编程方式生成表。 在我的例子中,我编写了一个简单的JSP来列出浏览器上的转换输入表(和收费公路表)或下载为csv以在MS Excel中显示。

构建这两个表有两个方向。

  1. 设计方向,您构建收费公路表所有可能的过渡,灰色不可达的过渡。 然后,仅为每个可到达的转换构建所有预期输入的Inputs表,并将行号作为转换ID。 每个转换id被转录到收费公交转换图的相应单元上。 但是,由于FSM是稀疏矩阵,因此并非所有转换ID都将用于收费公交转换映射的单元格中。 此外,转换ID可以多次使用,因为相同的转换条件可以应用于多对状态更改。

  2. 测试方向是反向的,您可以在其中构建Inputs表。 你必须为穷举过渡测试编写一般程序。
    该例程将首先读取转换排序表,以使状态机器进入入口点状态以开始测试循环。 在每个CURRENT状态下,它都准备好运行所有4320个转换ID。 在Turnpike转换映射中的CURRENT状态的每一行上,有效数量的列有效NEXT状态。

您可能希望例程循环从Inputs表中读取的所有4320行输入,以确保未使用的转换ID对CURRENT状态没有影响。 您想测试所有有效转换ID是否为有效转换。

但是你不能 – 因为一旦有效转换被泵入,它会将机器的状态改变为NEXT状态,并阻止你完成对之前CURRENT状态的其余转换ID的测试。 一旦机器改变状态,您必须再次从转换ID 0开始测试。

过渡路径可以是循环的或不可逆的,或者沿路径具有循环和不可逆部分的组合。

在测试例程中,您需要为每个状态设置一个寄存器,以记忆泵入该状态的最后一个转换ID。 每次测试达到有效的转换ID时,该转换ID都会保留在该寄存器中。 因此,当您完成一个循环并返回已经遍历的状态时,您开始迭代下一个转换ID大于寄存器中存储的转换ID。

您的例程必须处理转换路径的不可逆转部分,当机器进入最终状态时,它会从入口点状态重新开始测试,重复下一个转换ID大于存储的4320个输入为了一个州。 通过这种方式,您将能够详尽地发现机器的所有可能的过渡路径。

幸运的是,FSM是有效转换的稀疏矩阵,因为穷举测试不会消耗转换ID数量的整数组合x可能的平方数。 但是,如果您正在处理传统的FSM,其中视觉或温度状态无法反馈到测试系统,您必须在视觉上监视每个状态,就会出现困难。 这将是丑陋的,但我们仍然花了两周时间另外测试设备只通过有效的过渡。

如果您的FSM允许您通过简单的重置到达入口点并且应用转换ID,您可能不需要转换排序表(对于测试例程的每个入口点状态来读取以使机器进入所需的入口点)到入口点状态。 但是,让您的例程能够读取转换序列表是有用的,因为通常,您需要进入状态网络并从那里开始测试。

您应该熟悉转换和状态映射的使用,因为它对于检测机器的所有可能和未记录的状态非常有用,并且如果用户实际上希望它们变灰(过渡使得无效且状态无法到达),则会对用户进行访谈。

我的优点是它是一个新的设备,我可以选择设计状态机控制器来读取xml文件,这意味着我可以改变状态机的行为无论如何我想要的,实际上无论如何客户想要的和我能够确保未使用的转换ID真的无效。

有关状态机控制器的java列表http://code.google.com/p/synthfuljava/source/browse/#svn/trunk/xml/org/synthful 。 测试程序不包括在内。

根据要求进行测试。 如果在Completed为真时需要某个状态移动到某个其他状态,那么写一个测试会自动循环其他输入的所有组合(这应该只是一对for循环)来certificate其他输入是正确地忽略了 你最终应该为每个过渡弧进行一次测试,我估计这将在100或150次测试的数量级,而不是4000次。

您可以考虑调查基于模型的测试 。 在这种情况下,有一些工具可以帮助测试生成。 我通常推荐MBT 。

覆盖测试的蛮力似乎是一个开始。