【问题标题】:Compare object with datetime properties without microseconds将对象与日期时间属性进行比较,无需微秒
【发布时间】:2018-04-11 05:54:37
【问题描述】:

从新的 phpunit 版本开始,\DateTime 对象与微秒精度进行比较。这并不总是一个好主意,因为如果我有这样的对象:

class QueueItem
{
    public function __construct()
    {
        $this->setCreatedAt(new \DateTime('now', new \DateTimeZone('UTC')));
        $this->setUpdatedAt(new \DateTime('now', new \DateTimeZone('UTC')));
    }
}

我永远无法在我的测试中对整个对象使用assertEquals,因为日期时间属性总是会因微秒而不同。

仅比较对象时没什么大不了的(我可以分别比较每个字段)。但是问题来了:

$expectedQueue = [
    new QueueItem(),
    new QueueItem()
];

$this->repositoryWrite
    ->expects($this->once())
    ->method('save')
    ->with(...$expectedQueue);

with 比较整个参数数组,包括我无法预测的日期时间属性。

我唯一的解决方案是使用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 对象的比较器精度吗?

【问题讨论】:

  • 你试过DateTime::format()的方法吗? php.net/manual/en/datetime.format.php
  • assertEquals() 有一个可选的$delta 参数正是为了这个目的。
  • @SebastianBergmann 是的,但是在 MockObject 的 with 中没有这样的参数。这就是我要说的。
  • 但是with() 使用约束对象。只需将其与equalTo() 一起使用即可。
  • @SebastianBergmann,这将是一个非常复杂的约束。似乎 Hast 正在尝试同时测试多个事物。如果我明白了,QueueItems 将在被测类中使用,并且模拟中的断言旨在确认对象在类中的某个位置正确构造。显然equalTo 应该应用于QueueItem 实例的所有属性,以便为DateTime 使用正确的$delta。

标签: php unit-testing datetime phpunit


【解决方案1】:

测试时间可能相当困难 - 随着 PHP7 中默认的高分辨率的迁移,它确实变得更“有趣”了。在你的第一个例子中。这可能并不难 - 设置 $now = new \DateTime('now', new \DateTimeZone('UTC')); 并设置字段,但是在您正在测试的类中变得更加困难,您可能无法轻松控制。

但是,通过一点 PHP 命名空间 hack,您可以覆盖(在有限的基础上)一些基本语言函数,例如 time()sleep()。 Symfony 组件 'phpunit-bridge' 提供了一个 ClockMock 类,它可以覆盖 time() 以控制时钟 - 并将 sleep(10) 转换为 time()+10 调用 - 将测试中的实时暂停变为几乎为零-时间事件,并进行准确的比较。

它只能做这么多,并且不能覆盖 DateTime() 构造函数,但它很容易用将使用 time() 来设置它的东西替换它 -- $now = \DateTime::createFromFormat('U', (string) time()); ,因此可以使用时钟模拟。

您可以只安装PHPUnit Bridge,然后在您的测试中使用 ClockMock 类(稍微额外使用 time() 来创建新的 DateTime)。

我在我的测试中使用 PHPUnit-bridge,为了启用时钟模拟,我可以用“@group time-sensitive”注释测试类(如果它遵循命名它正在测试的类的常用风格带有Test\ 前缀和...Test 类名后缀),桥会识别它,并自动启用类的模拟 - 或者我可以在其他类的测试代码中手动启动它。

【讨论】:

    猜你喜欢
    • 2021-10-13
    • 1970-01-01
    • 2014-02-12
    • 2017-07-02
    • 1970-01-01
    • 2014-05-03
    • 1970-01-01
    • 2021-11-05
    • 1970-01-01
    相关资源
    最近更新 更多