PHPUnit:在一次测试中对模拟方法使用多个断言是不好的做法吗?

时间:2016-07-18 08:12:07

标签: php unit-testing phpunit assertions

我正在测试一个用于测试用户是否拥有给定电子邮件的对象。因此,在调用“tryEmail”方法时,它会向给定的电子邮件地址发送带有确认链接的消息。我的测试看起来像这样:

public function testSendingWasSuccessful() {

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

    $testType = 'test.type';
    $testEmail = 'test@example.com';
    $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 个答案:

答案 0 :(得分:1)

"每次测试一个断言的规则"是让您的测试专注于被测试代码的一个特定行为。在测试中有多个断言并不是坏事。

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

您测试类是为了确认代码的行为。您拥有的断言将是您手动执行的任何检查,以确保该类的行为符合您的预期。由于您希望以特定方式调用特定方法,因此您希望对它们进行断言。

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

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