Unit testing Meck和测试驱动开发工作流?

Unit testing Meck和测试驱动开发工作流?,unit-testing,testing,erlang,mocking,tdd,Unit Testing,Testing,Erlang,Mocking,Tdd,据我所知,TDD的典型工作流程W1如下: 开发功能测试; 运行测试并观察其失败; 开发功能; 再次运行测试并观察其通过; 重复1。 但是,对于Meck和其他模拟框架,工作流W2可以是: 为所有功能编写测试,并观察其失败; 在模拟对象的帮助下,开发一个完整的系统原型,包括所有功能及其交互; 再次运行测试并观察其通过; 逐步将每个模拟函数更改为真实函数。 我倾向于认为W2比W1有一些优势: 写得更快; 更容易集成,因为所有单元测试和集成测试都将提前编写;他们也会从一开始就过去; 因此,问题是: W2

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

开发功能测试; 运行测试并观察其失败; 开发功能; 再次运行测试并观察其通过; 重复1。 但是,对于Meck和其他模拟框架,工作流W2可以是:

为所有功能编写测试,并观察其失败; 在模拟对象的帮助下,开发一个完整的系统原型,包括所有功能及其交互; 再次运行测试并观察其通过; 逐步将每个模拟函数更改为真实函数。 我倾向于认为W2比W1有一些优势:

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


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

你不能只使用模拟来通过测试。如果这是可能的,要么你的测试不值它们的名,要么你不需要用真实的代码替换模拟,因为模拟已经完成了你想要代码做的事情


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

您不能独占地使用模拟来使测试通过。如果这是可能的,要么你的测试不值它们的名,要么你不需要用真实的代码替换模拟,因为模拟已经完成了你想要代码做的事情


mock通常用于将测试SUT下的系统(即要测试的类)与其依赖项隔离。依赖关系是模拟的,SUT不是。

W2假设您在开始之前已经完成了系统的正确设计-例如,我很少处于这样的位置。你必须提前做大的设计;这意味着你已经转移了开发过程中昂贵的部分,而不是消除它。如果你最初的设计恰巧有缺陷,而且会有缺陷,那么修复成本会很高

从一开始就通过的测试是一个bug,而不是一个特性。编写特别失败的测试是一个关键步骤——否则您将如何证明您编写的代码实际有效

我不能具体地与Meck交谈,但我可以与通用TDD工作流交谈,它自然地结合了各种各样的模拟对象。模拟对象的存在是为了让单元测试通过,而不管系统的其余部分是否工作。它们还为你接下来需要做的事情提供了自然的路标。为了使单元测试通过,您必须模拟一个类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的主要区别在于:

围绕单元测试周期的是另一个特性测试周期。 特性测试可能会失败一段时间;在其所有依赖项的集成完成之前,它无法通过。 我们对通过单元测试所编写的代码非常吝啬。由于模拟对象比真实的工作类更容易编写,这意味着我们在尝试这样做时模拟了我们发现的每个依赖项。 消除重复是非常必要的,也是我们经常用真实实现替换模拟对象的步骤。 最后一点可能需要更多的放大,最好不要通过示例重复整个测试驱动开发。发生的情况是,使第二个测试通过的最短方法通常是使用使第一个测试通过的模拟对象的副本,并调整一些参数。在消除重复步骤中,我们将这两个模拟对象合并为一个参数化对象。一旦我们重复了这个循环几次,我们构建的模拟对象与我们需要的真实对象几乎完全相同,因此我们可以使用its代码开始实现它迄今为止隐藏的依赖关系


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

W2假设您在开始之前已经完成了系统的正确设计-例如,一个职位I很少是I N你必须提前做大的设计;这意味着你已经转移了开发过程中昂贵的部分,而不是消除它。如果你最初的设计恰巧有缺陷,而且会有缺陷,那么修复成本会很高

从一开始就通过的测试是一个bug,而不是一个特性。编写特别失败的测试是一个关键步骤——否则您将如何证明您编写的代码实际有效

我不能具体地与Meck交谈,但我可以与通用TDD工作流交谈,它自然地结合了各种各样的模拟对象。模拟对象的存在是为了让单元测试通过,而不管系统的其余部分是否工作。它们还为你接下来需要做的事情提供了自然的路标。为了使单元测试通过,您必须模拟一个类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的主要区别在于:

围绕单元测试周期的是另一个特性测试周期。 特性测试可能会失败一段时间;在其所有依赖项的集成完成之前,它无法通过。 我们对通过单元测试所编写的代码非常吝啬。由于模拟对象比真实的工作类更容易编写,这意味着我们在尝试这样做时模拟了我们发现的每个依赖项。 消除重复是非常必要的,也是我们经常用真实实现替换模拟对象的步骤。 最后一点可能需要更多的放大,最好不要通过示例重复整个测试驱动开发。发生的情况是,使第二个测试通过的最短方法通常是使用使第一个测试通过的模拟对象的副本,并调整一些参数。在消除重复步骤中,我们将这两个模拟对象合并为一个参数化对象。一旦我们重复了这个循环几次,我们构建的模拟对象与我们需要的真实对象几乎完全相同,因此我们可以使用its代码开始实现它迄今为止隐藏的依赖关系

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