【问题标题】:Meck and Test Driven Development workflow?Meck 和测试驱动开发工作流程?
【发布时间】:2012-08-23 06:14:07
【问题描述】:

据我所知,TDD的典型工作流程(W1)如下:

  1. 为函数开发测试;
  2. 运行测试并观察它是否失败;
  3. 开发功能;
  4. 再次运行测试并观察它是否通过;
  5. 重复 1。

但使用 Meck 和其他模拟框架,工作流 (W2) 可能如下:

  1. 为所有函数编写测试并观察它们是否失败;
  2. 借助模拟对象开发完整的系统原型(所有功能及其交互);
  3. 再次运行测试并观察它们是否通过;
  4. 逐步将每个模拟函数更改为真实函数。

我倾向于认为 W2 比 W1 有一些优势:

  • 写得更快;
  • 更容易集成,因为所有测试(单元测试和集成测试)都将提前编写;他们也会从一开始就过去;

所以,问题是:

W2 真的具备上述优势吗?如果不是,我如何将 Meck 作为既定例程纳入我的日常开发中,即使用它的工作流程是什么?还是我应该在没有任何指导的情况下随意使用 Meck?

【问题讨论】:

    标签: unit-testing testing erlang mocking tdd


    【解决方案1】:

    您不能只使用模拟来使测试通过。如果可以的话,要么你的测试名不副实,要么你不需要用“真实”代码替换模拟,因为模拟已经做了你想让代码做的事情。

    Mocks 通常用于将被测系统 (SUT) (即您要测试的类)与其依赖项隔离开来。依赖项是模拟的,SUT 不是。

    【讨论】:

      【解决方案2】:

      W2 假设您在开始之前已经完成了系统的正确设计——我很少处于这个位置。您必须预先进行大型设计; IME 这意味着您已经转移了开发过程中昂贵的部分,而不是消除了它。而且,如果您的初始设计碰巧有缺陷(而且确实会),那么恢复成本将非常昂贵。

      从一开始就通过的测试是错误,而不是功能。编写专门失败的测试是至关重要的一步——您将如何证明您编写的代码确实有效?

      我不能专门与 Meck 交谈,但我可以谈论一般的 TDD 工作流程,它自然地结合了各种风格的模拟对象。模拟对象的存在是为了让单元测试通过,而不管系统的其余部分是否正常工作。它们还为您接下来需要做的事情提供自然的路标。为了使单元测试 A 通过,您必须模拟一个 B 类。显然,接下来要做的就是实现一个 B 类,它的行为方式与其模拟所描述的方式相同。

      您的 W1 省略了一些关键步骤,包含这些步骤可能会阐明模拟对象在 TDD 中所扮演的角色。以伪 Perl 为例,TDD 看起来更像这样:

      while (not $project->is_feature_complete()) {
        my $feature_test = write_feature_test();
        die "You screwed up" if $feature_test->does_pass();
        $feature_test_suite->add($feature_test);
        while (not $feature_test_suite->does_pass()) {
          my $test = write_unit_test();
          die "You screwed up" if $test->does_pass();
          $unit_test_suite->add($test);
          while (not $unit_test_suite->does_pass()) {
            write_exactly_enough_code_to_pass_unit_test();
            while ($project->has_duplication()) {
              $project->eliminate_duplication();
            }
          }
        }
      }
      

      与您的 W1 的主要区别是:

      • 围绕单元测试循环的是另一个功能测试循环。
      • 功能测试可能会持续失败一段时间;在其所有依赖项的集成完成之前,它无法通过。
      • 我们为通过单元测试而编写的代码非常节俭。而且由于模拟对象比真正的工作类更容易编写,这意味着我们在尝试这样做时会模拟出我们发现的每个依赖项。
      • 消除重复至关重要,这也是我们经常用真实实现替换模拟对象的步骤。

      最后一点可能需要进一步放大(最好不要重复示例测试驱动开发1全部)。发生的情况是,使第二次测试通过的最短方法通常是使用模拟对象的副本,该模拟对象使第一次测试通过并调整了一些参数。在“消除重复”步骤中,我们将这两个模拟对象组合成一个参数化对象。一旦我们重复该循环几次,我们构建的模拟对象与我们需要的真实对象几乎完全相同,因此我们可以使用它的代码开始实现它迄今为止隐藏的依赖关系。

      在所有这些中,模拟对象的作用几乎总是相同的:允许您通过单元测试而不必一次实现整个系统。在我看来,使用它们预先实现整个系统似乎浪费了它们的大部分效用,同时也让您面临大型预先设计的常见风险。我不认为你会得到你认为你会得到的好处;充其量,您更改会计以减少“编码”所花费的时间。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-21
        • 1970-01-01
        • 2013-01-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-01
        • 2011-09-09
        相关资源
        最近更新 更多