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

PHPUnit: Is it a bad practice to use multiple assertions on mocked methods in one test?

我正在测试一个旨在测试用户是否拥有给定电子邮件的对象。因此,在调用 "tryEmail" 方法时,它会向给定的电子邮件地址发送一条带有确认 link 的消息。我的测试如下所示:

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 的这个测试,所以看起来我在这里做错了什么。然而与此同时——我如何在不引入这种耦合的情况下检查上述断言?或者也许我应该让这个未经测试?

我这样做是对还是错?我该如何改进它?什么时候真正在模拟上使用断言?

添加: 我只是有一个想法 - 测试 class 是否应该测试 实现 (如果实现符合接口)?这意味着在测试中描述实现实际上是一件好事,因为它可以确保实现正常工作。这也意味着实施的耦合程度将转移到测试中,这是不可避免的。我错了吗?

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

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

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

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

您似乎也没有在此测试中使用第三个参数,因此似乎没有必要。