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
}
}
}
或者另一种选择不是注入版本,而是注入一个提供者(如果你知道你会有一些复杂的版本控制逻辑——所以你可以在 Module
和 Provider
视情况而定):
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 具有隐藏的依赖性,在测试中有些棘手,因此不太干净,需要更多的努力来维护,但将所有选择内容隐藏在自身内部,使常规使用更容易。
这个 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
}
}
}
或者另一种选择不是注入版本,而是注入一个提供者(如果你知道你会有一些复杂的版本控制逻辑——所以你可以在 Module
和 Provider
视情况而定):
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 具有隐藏的依赖性,在测试中有些棘手,因此不太干净,需要更多的努力来维护,但将所有选择内容隐藏在自身内部,使常规使用更容易。