Начиная с новых версий phpunit \ DateTime объекты сравниваются с точностью до микросекунд. Это не всегда хорошая идея, потому что, если у меня есть такой объект:
class QueueItem { public function __construct() { $this->setCreatedAt(new \DateTime('now', new \DateTimeZone('UTC'))); $this->setUpdatedAt(new \DateTime('now', new \DateTimeZone('UTC'))); } }
Я никогда не смогу использовать assertEquals
для всего объекта в своих тестах, потому что свойства datetime всегда будут отличаться из-за микросекунд.
Это не большое дело, когда просто сравнивать объекты (я могу сравнить каждое поле отдельно). Но здесь возникает проблема:
$expectedQueue = [ new QueueItem(), new QueueItem() ]; $this->repositoryWrite ->expects($this->once()) ->method('save') ->with(...$expectedQueue);
сравнивает весь массив аргументов, включая свойства datetime, которые я не могу предсказать.
Единственное, что у меня есть, это использование willReturnCallback
и сравнение каждого аргумента отдельно:
$this->repositoryWrite ->expects($this->once()) ->method('save') ->willReturnCallback(function(...$queue) use ($expectedQueue) { /** @var QueueItem[] $queue */ foreach ($queue as $k => $queueItem) { $this->assertEquals($expectedQueue->getSomeProp(), $queueItem[0]->getSomeProp()); } });
Это выглядит … странно. Есть ли другое решение для этого? Могу ли я изменить точность компаратора для объектов \ DateTime?
Время тестирования может быть довольно сложным – и, перейдя на стандартное разрешение по умолчанию в PHP7, оно стало более «интересным». В первом примере. это может быть не так сложно – установите $now = new \DateTime('now', new \DateTimeZone('UTC'));
и установите поля, но это становится намного сложнее внутри класса, который вы тестируете, чтобы у вас не было такого простого контроля.
Однако с небольшим взломом PHP Namespace вы можете переопределить (на ограниченной основе) некоторые функции базового языка, такие как time()
и sleep()
. Компонент Symfony, « phpunit-bridge », предоставляет класс ClockMock, который может переопределить time()
для управления часами – и превратить sleep(10)
в вызов time()+10
– превращение паузы в реальном времени в тест почти нулевое время и точное сравнение.
Он может делать только так много и не может переопределить конструктор DateTime()
, но его легко заменить тем, что будет использовать time()
для его установки – $now = \DateTime::createFromFormat('U', (string) time());
и поэтому можно управлять с помощью ClockMocking.
Вы можете просто установить PHPUnit Bridge , а затем просто использовать класс ClockMock в своих тестах (с этим небольшим дополнительным использованием времени () для создания нового DateTime).
Я использую PHPUnit-мост в своих тестах, и чтобы включить Mocking Clock, я могу просто аннотировать тестовый класс с помощью «@group time-sensitive» (если он соответствует обычному типу присвоения имени классу, который он тестирует с помощью Test\
prefix и ...Test
suffname), и мост узнает это и автоматически включит насмешку над классом – или я могу запустить его вручную в тестовом коде для других классов.