测试大型状态机的策略是什么?

时间:2010-04-22 13:15:44

标签: c# unit-testing state-machine mspec

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

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

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

[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<meetingRequestedNode>();
    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)的内容。

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


修改:我正在使用所有建议,并在找到效果最佳时给出答案。

9 个答案:

答案 0 :(得分:6)

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

我眼中最大的问题是:

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

有太多的事情发生了。输入使代码难以测试。你已经说过将它分解成更易于管理的区域会很痛苦,但是如果不是更加痛苦的话,那么在开始时测试这么多的逻辑就好了。在您的情况下,每个单元测试都涵盖远远太多

question I asked about testing large methods本质上是相似的,我发现我的单位太大了。你最终还是会进行很多测试,但它们会更小,更易于管理,覆盖范围更小。这只是一件好事。

测试旧版代码

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

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

答案 1 :(得分:4)

全对测试

要限制要测试的组合数量并合理保证您已涵盖最重要的组合,您应该查看所有配对测试。

  

全对测试背后的原因   是这样的:最简单的错误   程序通常由a触发   单输入参数。下一个   最简单的错误类别包括   那些依赖于互动的人   在参数对之间,可以   被所有对测试抓住   涉及之间相互作用的错误   三个或更多参数是   逐渐减少共同2,而在   同时逐步增加   通过详尽的找到昂贵   测试,其中有限制   所有可能的详尽测试   输入。

另请查看a previous answer here(无耻插件)以获取更多信息以及指向全配对的链接。作为工具。

示例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

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:3)

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

我建议遵循:

  1. 测试大多数情况, 边界条件。
  2. 测试SUT的一些关键部分 分开。
  3. 在QA发现错误时添加测试用例 或在生产中。
  4. 在这种特殊情况下,最好的方法是测试系统如何从一种状态切换到另一种状态。创建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

答案 4 :(得分:3)

使用SpecExplorerNModel

答案 5 :(得分:3)

答案 6 :(得分:1)

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

答案 7 :(得分:1)

您可以考虑调查Model Based Testing。在这种情况下,有一些工具可以帮助测试生成。我通常建议MBT

答案 8 :(得分:0)

使用覆盖测试的蛮力似乎是一个非常开始。