我正在测试一个用于测试用户是否拥有给定电子邮件的对象。因此,在调用“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
的这个测试,所以看起来我在这里做错了。但与此同时 - 如何在不引入这种耦合的情况下检查上述断言?或者我应该留下未经测试的?
我这样做是对还是错?我怎么能改进它?什么时候真的在模拟上使用断言?
已添加:我刚想到 - 测试类是否应该测试实现(如果实现符合接口)?这意味着在测试中描述实现实际上是一件好事,因为它确保实现正常工作。这也意味着实施的耦合程度将被延续到测试,并且是不可避免的。我错了吗?
答案 0 :(得分:1)
"每次测试一个断言的规则"是让您的测试专注于被测试代码的一个特定行为。在测试中有多个断言并不是坏事。
当使用模拟对象时,我更喜欢对被替换的方法进行某种断言。这样我就可以确保系统按预期使用依赖项。
您测试类是为了确认代码的行为。您拥有的断言将是您手动执行的任何检查,以确保该类的行为符合您的预期。由于您希望以特定方式调用特定方法,因此您希望对它们进行断言。
我在测试中看到的问题是你有一个模拟对象返回一个模拟对象。这通常是代码气味,这意味着您没有传递正确的依赖项。您可以将LT\EmailConfirmation\Model\ConfirmationObjectInterface
对象的创建移出方法,并将其作为方法的依赖项传递。用此对象替换方法的前两个参数。
在此测试中,您似乎根本没有使用第三个参数,所以它似乎没有必要。