2016-07-18 17 views
4

我正在测试一个对象,该对象旨在测试用户是否拥有给定的电子邮件。因此,在调用“tryEmail”方法时,它会向给定的电子邮件地址发送带有确认链接的消息。我的测试如下所示:PHPUnit:在一个测试中对模拟方法使用多个断言是否是一种不好的做法?

public function testSendingWasSuccessful() { 

    $confirmationObject = $this->getMock('LT\EmailConfirmation\Model\ConfirmationObjectInterface'); 

    $testType = 'test.type'; 
    $testEmail = '[email protected]'; 
    $testData = []; 

    // EmailTester should create a new confirmation object. 
    $this->manager->expects(static::once()) 
     ->method('create')->with($testType, $testEmail) 
     ->willReturn($confirmationObject); 

    // Then it should send the confirmation message. 
    $this->mailer->expects(static::once()) 
     ->method('send')->with(static::identicalTo($confirmationObject)) 
     ->willReturn(true); 

    // And save the confirmation object. 
    $this->manager->expects(static::once()) 
     ->method('save')->with(static::identicalTo($confirmationObject)); 

    $tester = new EmailTester($this->repository, $this->manager, $this->confirmationHandler, $this->mailer); 

    static::assertTrue($tester->tryEmail($testType, $testEmail, $testData)); 
} 

现在您可以看到它可能有什么问题 - 它包含多个断言。为什么我决定在一次测试中使用这些断言?因为他们彼此依赖。因此,只有在创建新的确认对象时才应发送确认消息,并且只有在确认消息已发送时才应保存确认对象,最后,使用这些模拟方法的“tryEmail”方法的输出正在断言。

但是,我觉得我不小心只是用我的断言描述了“tryEmail”方法的实现。但是,似乎需要对这种方法进行全面的介绍,并且确保它始终按照应有的方式工作。我可以想象如果我将删除任何这些断言的错误传递。例如:static::identicalTo($confirmationObject)这基本上是:check if the object passed to the mailer is the same as the one created before。如果我改变邮件的界面,我将不得不改变EmailTester的这个测试,所以看起来我在这里做错了什么。然而,在同一时间 - 我怎样才能检查上述断言,而不引入这种耦合?或者,也许我应该离开这未经考验?

我在做对还是错?我怎么能改进它?何时真的使用嘲讽断言?

补充:我只是有一个想法 - 是不正确的,测试类应该测试实施(如果实现与接口符合)?这意味着在测试中描述实现实际上是一件好事,因为它确保实现正确工作。这也意味着实施的耦合水平将被转移到测试中,并且是不可避免的。我错了吗?

回答

1

“每个测试一个断言”的规则是让您的测试集中在正在测试的代码的一个特定行为上。在测试中有多个断言并不是一件坏事。

当使用模拟对象时,我更喜欢对被替换的方法进行某种断言。这样我确保系统将按照预期使用依赖关系。

您正在测试的课程是确认您的代码的行为。您拥有的断言将是您手动进行的任何检查,以确保该班级的行为符合您的预期。既然您期望以特定的方式调用特定的方法,您希望对它们有一个断言。

我在测试中看到的问题是您有一个模拟对象返回一个模拟对象。这通常是一种代码异味,意味着你没有传递正确的依赖关系。您可以将创建的LT\EmailConfirmation\Model\ConfirmationObjectInterface对象移出该方法,并将其作为方法的依赖项传递。用此对象替换方法的前两个参数。

在这个测试中,您似乎也没有使用第三个参数,所以它似乎没有必要。

+0

感谢您的回答!您对'LT \ EmailConfirmation \ Model \ ConfirmationObjectInterface'的评论给了我一些想法。我有意识地设计它,以尽量减少使用此对象时所需的工作量。现在我几乎可以在控制器中使用一个函数'tryEmail'并完成。否则,我将不得不拉动管理器,并在控制器中创建确认对象,然后通过'tryEmail'方法传递它,这将只是我的Controller中的更多代码,这似乎是一个坏主意。你确定这会有所改进吗? –

+0

我很有信心。我为你的对象增加了灵活性,'EmailTester'现在可以采用不同类型的对象。它也让他们专注。如果您需要发送不同的电子邮件类型,则此对象应该能够毫无问题地处理它。 – Schleis

相关问题