为什么 Memcache::get() 的反射与文档不同?

Why is the Reflection for Memcache::get() different from the doc?

我正在尝试向 class 添加单元测试,该 class 使用 Memcache 作为在本地 memcached 守护程序上获取和存储密钥的服务。

我的问题是,尽管代码 运行 没问题,但当我尝试模拟 Memcache class 并调用 get() 方法时,我得到了这种错误:

ArgumentCountError: Too few arguments to function Mock_Memcache_b25e34cb::get(), 1 passed in /path/to/file.php on line 64 and exactly 3 expected

我怀疑是反射错误,所以我尝试了一个小脚本来测试它:

<?php
$class = new ReflectionClass(Memcache::class);
$getMethod = $class->getMethod('get');
$getParams = $getMethod->getParameters();
foreach($getParams as $param) {
    var_dump((string) $param);
}

我得到的输出是

string(35) "Parameter #0 [ $param0 ]"

string(36) "Parameter #1 [ &$param1 ]"

string(36) "Parameter #2 [ &$param2 ]"

但是,在 the official PHP doc 找到的文档指出签名应该是:

Memcache::get(string $key, int &$flags = ?): string

Memcache::get(array $keys, array &$flags = ?): array

如何解释差异?为什么我的代码 运行 在生产环境中运行良好,但在 UT 环境中却失败了?除了为 Memcache 创建我自己的 mock class 之外,有没有办法解决这个问题?谢谢!

仅供参考,我目前获取模拟的方法是这样的:

protected function createMock($originalClassName)
    {
        return $this->getMockBuilder($originalClassName)
                    ->disableOriginalConstructor()
                    ->disableOriginalClone()
                    ->disableArgumentCloning()
                    ->disallowMockingUnknownTypes()
                    ->getMock();
    }

public function existsReturnsTrueOrFalse($value, $expected)
    {
        $memcacheMock = $this->createMock(\Memcache::class);
...
    }

How can the discrepancy be explained?

大多数情况下,这只是扩展的文档不是最新的。

另一种解释可能是库反射错误。

看到反射的输出,我想说文档比反射更新。

Why does my code run fine on production but fails on the UT?

您的问题的单元测试的生产和测试之间的区别 运行 是模拟的创建:在生产中您不创建模拟,因此它不会失败。

Is there a way for me to solve this problem other than creating my own mock class for Memcache?

实际上创建您自己的模拟 class 听起来不是个坏主意。但是,您可能会考虑更进一步:

现在生产代码有 Memcache class 作为依赖项。你的测试已经告诉你,它的接口不稳定,依赖项拒绝测试(使用你的标准工具,如模拟)。

出于诸如此类的原因,通常建议包装第三方库。这可能是一个边缘案例,因为这与 PHP 扩展相关,该扩展可能不会那么不稳定,但也许包装为您提供了更多的开发、测试和维护空间。

您可以将您使用的 Memcache class 的 public 接口部分提取到接口本身中,例如内存缓存接口。然后,您可以创建一个实现接口的轻量级包装 -class,并在后台将方法调用委托给 Memcache 实例。

然后,您将生产代码中的 Memcache 对象替换为您的包装对象,并将 Memcache 的类型提示替换为您的 MemcacheInterface接口。

在测试中,您从界面创建模拟。

这将

  1. 记录每个使用的方法和参数(您在界面上执行此操作)。
  2. 允许在从(明确的)界面创建模拟时在 Phpunit 中创建模拟。
  3. 通过新包装器在中心位置访问第三方库的接口 class - 即使在生产中。
  4. 允许您将来在您的应用程序中切换到不同的 memcache 第三方库(但您不能,我为简洁起见提到它)。