【问题标题】:How to unit-test sequential logic?如何对时序逻辑进行单元测试?
【发布时间】:2009-06-08 16:28:27
【问题描述】:

假设我有具有以下方法的 Car 类:

  • Lo​​adGasoline(Iuel gas)
  • InsertKey(IKey 键)
  • StartEngine()
  • IDrivingSession Go()

Car 的目的是配置并返回一个 IDrivingSession,应用程序的其余部分将使用该 IDrivingSession 来驾驶汽车。如何对我的汽车进行单元测试?

看起来它需要完成一系列操作才能调用 Go() 方法。但我想分别测试每种方法,因为它们都有一些重要的逻辑。我不想有一堆像

这样的单元测试
Test1: LoadGasoline, Assert

Test2: LoadGasoline, InsertKey, Assert

Test3: LoadGasoline, InsertKey, StartEngine, Assert

Test4: LoadGasoline, InsertKey, StartEngine, Go, Assert

是否有更好的方法来对顺序逻辑进行单元测试,或者这是我的汽车设计的问题?

--- 编辑 ---- 感谢所有的答案。正如许多人所注意到的,我也应该对无效场景进行测试,我也有这些,但这个问题的重点是如何测试有效序列。

【问题讨论】:

    标签: unit-testing language-agnostic


    【解决方案1】:

    我认为每种方法都应该分别独立地进行测试。

    恕我直言,您应该为每种情况准备好环境,因此如果您更改 LoadGasoline 方法,只有 LoadGasoline 测试会中断,您不需要看到所有测试都因为一个错误而中断。

    我不知道你的 Car 的状态如何,但是,在 InsertKey 之前,你应该准备一个方法,比如 car.SetTotalGasoline(20);或在此方法中设置的任何变量,但不依赖于方法 LoadGasoline 的复杂逻辑。

    稍后您将需要一个测试(在这种情况下,不是单元测试)来测试所有序列。

    【讨论】:

    • 但是如果您必须初始化对象以使汽车进入正确的状态,您最终将调用 LoadGasoline 方法。如果您修改实现,您可能不仅会破坏它的单元测试,还会破坏所有其他测试的设置代码。
    • 你可以扩展汽车类来做到这一点。就像做一些类来帮助扩展 Car 类的测试,但有更多的方法来帮助测试
    • 我最喜欢这种方法,它似乎非常符合我对良好设计和单元测试的想法......如果没有更好的结果,稍后将标记为答案。
    【解决方案2】:

    一些单元测试框架允许您指定在实际测试开始之前运行的设置代码。

    这允许您在运行测试之前让目标对象进入正确的状态。这样,您的测试可以根据您正在测试的特定代码而不是在您可以运行测试之前所需的代码来通过或失败。

    因此,您的测试序列将如下所示:

    Test1: 
        LoadGasoline, Assert
    
    Test2 Setup:
        LoadGasoline
    
    Test2:
        InsertKey, Assert
    
    Test3 Setup: 
        LoadGasoline, InsertKey
    
    Test3:
        StartEngine, Assert
    
    Test4 Setup: 
        LoadGasoline, InsertKey, StartEngine
    
    Test4:
        Go, Assert
    

    实际上,由于测试都是按顺序运行的,因此如果之前的测试通过,则测试的设置不会失败。

    话虽如此,您还应该测试预计不会起作用的失败案例,但这是一个不同的问题。

    【讨论】:

    • 这是我最初所做的,但是(至少对于 MSTest)如果其设置逻辑失败,则单元测试会失败,因此结果与我在每个方法中都有所有步骤一样。
    【解决方案3】:

    你为什么不想要所有这些测试?

    Go 在你之前或之后调用它会有非常不同的行为,比如InsertKey,对吧?所以在我看来,你应该测试这两种行为。

    【讨论】:

    • 我认为他的问题是测试 4 正在测试与测试 1,2 和 3 相同的内容。因为所有操作都以相同的顺序进行。在两个不同的测试中,他没有在插入键之前或键之后调用 Go。我同意测试 4 重复测试 1-3 的操作。我同意你的观点,你需要改变订单。所以你试着驾驶一辆没有启动的汽车。
    【解决方案4】:

    这是不情愿的,但有时这就是你需要做的。如果您无法伪造被测系统以使其认为处于稍后状态,那么您需要通过相同的步骤使其进入该状态。如果不了解您的测试内容,就不清楚如何伪造不同的状态。

    使这种情况变得可以容忍的一种方法是使用提取方法重构一个状态的测试,以便可以使用相同的代码来准备下一个测试。

    【讨论】:

      【解决方案5】:

      我可能会有

      Test 1: LoadGasoline, Assert, InsertKey Assert, StartEngine Assert, Go Assert
      Test 2: LoadGasoline, Go, Assert
      Test 3: Go, Assert
      Test 4: StartEngine, Go, Assert
      

      根据实际对象,我可能不会尝试做所有的排列,但我会进行一次成功的测试,然后我会测试我的边缘案例。

      编辑:

      经过一番思考,我可能会进行如下测试:

      • 启动没有汽油的车钥匙
      • 用油和错误的钥匙启动汽车
      • 用油门和右键启动汽车(上面的测试 1)
      • 在启动汽车前推动 Peddle。

      【讨论】:

      • +1 这看起来更像是行为驱动的开发,这可能是他想要关注的。
      • 如果 LoadGasoline 后来重构出了一个 bug 怎么办?在您的示例中,Test1 和 Test2 都会失败,这是我认为应该避免的。
      • 是的,如果你想测试更多的集成风格的行为风格,或者你想要一个更纯粹的孤立测试,它会失败。要获得隔离测试,您需要确保可以将对象设置为正确的状态。根据您的型号,这可能有效也可能无效
      【解决方案6】:

      从技术上讲,您至少应该使用以下测试:

      testLoadGasoline
      
      testInsertKeyGasolineNotLoaded
      
      testStartEngineKeyNotInserted
      
      testGoEngineNotStarted
      
      testGo
      

      如果可以直接查看中间步骤可以添加

      testInsertKeyGasolineLoaded
      
      testStartEngineKeyInserted
      

      请注意,如果您可以直接设置状态(取决于语言和设计),那么 testInsertKeyGasolineLoaded 可能实际上不会调用 LoadGasoline。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-12-27
        • 1970-01-01
        • 2013-03-17
        • 2014-06-27
        • 2015-06-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多