伦敦TDD:在需求发生变化时处理不一致行为

时间:2014-07-10 07:36:04

标签: unit-testing tdd

我刚才知道有两种TDD风格是伦敦和经典,之后关于TDD的问题更加清晰,这就是我问这个问题的原因。

来自这篇文章http://codemanship.co.uk/parlezuml/blog/?postid=987
在第二个示例中,注册示例,如果我的客户在会议的第一时间定义该电子邮件不包含任何期间,例如。 my.name@example.com无效,因为它具有我和名称之间的句点,然后我们实现属于该要求,与注册示例相同,并且实现工作很长时间。

有一天,客户来找我并告诉我他希望允许在电子邮件中存在时间,所以我意识到我需要导航到注册控制器的测试并更改添加更多测试以确保通过模拟Validator.validateEmail(email)以返回false 来满足新要求但我忘记更改Validator.validateEmail以检查周期然后运行测试,所有测试都是绿色(因为我们模拟结果)但是程序仍然是bug。

我承认这个例子是一个小程序,可以快速找到bug但是如果这个问题出现在某个难以找到的地方怎么办?

我不知道这是不是一个愚蠢的问题,当我需要处理复杂的软件时,它发生在我身上很多次,当需求发生变化时我总是确保我改变所有的测试相关但有一些地方的测试,我没有意识到这样的例子。

那么,你有同样的问题吗?你怎么处理它?<​​/ p>

PS。我在PHP中使用PHPUnit创建了一个代码示例,请参阅http://pastebin.com/RXP8i6Y3。请使用AllTest.php保存并使用phpunit AllTest.php运行以查看结果并尝试取消注释TODO代码。

更新
我改变某些事情时,我无法信任我的代码。当应用程序增长时,测试用例的数量增长(有时超过5000+测试),并且我们在测试中的某处使用模拟。当我们要改变某些东西时,有时模拟对象不能反映测试中的任何错误,你可以看到测试的数量是多少,它会更难维护。

1 个答案:

答案 0 :(得分:1)

当需求发生变化时,您不会修改现有测试。您为新要求编写了一个新测试,然后在TDD周期 red-green-refactor 之后实现功能/更改。

当您运行整套测试时,其中一些测试会因旧逻辑发生变化而失败。当发生这种情况时,您检查测试并查看是否有意义修改它们或更好地删除它们,因为它们已从需求角度过时。

从您的问题中不清楚您想要测试什么。但是,如果你模拟EmailValidator而不是实际测试控制器,那么当验证器的结果是false而不是验证器本身时,控制器的行为如何。

使用模拟框架时,参数的实际值确实非常重要,因此您需要注意这一点。

如果在创建第一次运行时没有失败的测试时正确执行TDD,则要么重新测试现有要求,要么测试有错误。在添加新要求时,您必须始终首先看到测试失败,否则您应该考虑使用红色标记来更仔细地查看测试/代码或要求。

在评论链接

更新


你没有测试正确的东西。如果您想检查电子邮件是否经过正确验证,您可以使用Validator进行测试,这很好:

public function test_validate_with_period_return_false() {
    $inst = new Validator();
    $this->assertSame(false, $inst->validate('with.period@example.com'));
  }

  public function test_validate_without_period_return_true() {
    $inst = new Validator();
    $this->assertSame(true, $inst->validate('withoutperiod@example.com'));
  }

  // //This is a test that support new requirement of validator
  // //even i add this test and remove test_validate_with_period_return_false
  // //how about mock of Validator in test above? how do I known where is using this class
  // //Mock don't reflect error after this method changed
  // public function test_validate_after_requirement_changed_period_must_allow() {
  //   $inst = new Validator();
  //   $this->assertSame(true, $inst->validate('with.period@example.com'));
  // }

取消注释上一次测试后,您还应该从Validator中替换实际逻辑,然后删除第一个测试(当您在电子邮件中有句点时返回false)。

现在,如果你想测试控制器,你的测试应该听起来不像:

public function test_openUserAccount_not_allow_period_in_email()
public function test_openUserAccount_return_true_with_period_in_email()

他们应该听起来像

public function test_openUserAccount_ThrowsException_WhenEmailIsInvalid()
public function test_openUserAccount_return_true_when_emailIsValid()

然后,您作为参数传递的电子邮件无关紧要,因为您始终从模拟中返回true或false。实际的电子邮件验证在验证器测试中单独测试,现在您在电子邮件验证器(与电子邮件的值无关)返回true或false时测试控制器逻辑。

如果你想保持你的测试,你不应该嘲笑电子邮件验证器。只需使用真实的东西。它完全在内存中运行,仍然很快。你放弃了完美的隔离并一起测试两个类(控制器和验证器),但你获得了其他部分,比如在没有太多测试中断的情况下更容易重构。