PHP 7 中的猴子补丁

Monkey patching in PHP 7

这个 four year old question 使用第三方库,我有点怀疑。

仅出于测试目的,我想重新定义我的 classes 之一的静态方法。举个例子:

class Driver {
   public static function getVersion() : string
   {
        // Retrieves a version from a system executable
        return some_system_call();
   }
}

class Module {
   public function methodToTest()
   {
       if (Driver::getVersion() === '4.0.0') {
          // allow for additional options/methods
       } else {
          // use a subset
       }
   }
}

我需要 Driver::getVersion 到 return 不同的版本字符串。我通常会嘲笑 class,但由于这既不是注入也不是实例,因此它不会起作用。

我可以更改源代码,添加方法和 属性 测试,这样被测试的 classes 永远不需要调用 Driver,但是,在我看来,重构代码只是为了进行测试 "work" 不是解决方案。

我正在考虑创建另一个 Driver class 并以某种方式加载它来代替原来的。

我该怎么做?

class Driver {
    private static $testVersion;

    public static function setTestVersion(string $testVersion = null)
    {
        static::$testVersion = $testVersion;
    }

    public static function getVersion() : string
    {
        if (static::$testVersion !== null) {
            return static::$testVersion;
        }
        // Retrieves a version from a system executable
        return some_system_call();
    }
}

您可以注册一个 class 加载程序,它以某种方式知道测试并从不同的位置加载修改后的驱动程序 class。

你可能想像这样使用:

class Module
{
   private $version;

   public function __construct($version){
      $this->version = $version;
   }

   public function methodToTest()
   {
       if ($this->version === '4.0.0') {
          // allow for additional options/methods
       } else {
          // use a subset
       }
   }
}

或者另一种选择不是注入版本,而是注入一个提供者(如果你知道你会有一些复杂的版本控制逻辑——所以你可以在 ModuleProvider 视情况而定):

class Module
{
   private $versionProvider;

   public function __construct($provider){
      $this->versionProvdier = $provider;
   }

   public function methodToTest()
   {
       if ($this->versionProvider->getVersion() === '4.0.0') {
          // it could be even $this->versionProvider->newFeaturesAreSupported()
       } else {
          // some other stuff
       }
   }
}

还有一个可能正在实施一些代理 class,例如

class Module
{
   public function methodToTest()
   {
       $myMonostateProxy = new MyMonostateProxy();
       $version = $myMonostateProxy->getVersion();
       if ($version === '4.0.0') {
          // allow for additional options/methods
       } else {
          // use a subset
       }
   }
}

所以你可以单独模拟你的monostate(可能通过反射privtates或者通过它的public接口,无论如何不要忘记tearDown它)。它的真正实现只会调用无法控制的 Driver::getVersion().

我认为前两个选项更清晰,但需要一些创建工作(因为您需要一些注入才能执行)。 Third 具有隐藏的依赖性,在测试中有些棘手,因此不太干净,需要更多的努力来维护,但将所有选择内容隐藏在自身内部,使常规使用更容易。