robvanaarle / php-object-seam
一种简单的方法,通过最小化代码更改来创建对象缝隙,从而在测试遗留PHP代码时打破依赖关系
Requires
- php: >=7.0
Requires (Dev)
This package is not auto-updated.
Last update: 2024-09-30 11:46:42 UTC
README
PHP Object Seam 提供了一种简单的方法来在PHP中创建对象缝隙。这些用于遗留代码,以最小更改打破依赖关系,以便测试受测试的类。
由于依赖关系,遗留代码难以扩展和维护。理想情况下,它需要重构,但并不总是有时间这样做。为了对遗留代码的新功能或错误修复有信心,需要自动测试。但是遗留代码往往没有(足够的)自动测试。由于相同的依赖关系,这些测试也难以添加。为了放置测试,必须首先通过更改代码来打破依赖关系。这些更改也需要测试,但由于相同的原因,这很困难。
在他的书《有效地与遗留代码协作》中,Michael Feathers 定义了一个缝隙为“一个可以改变程序行为而不改变代码的地方”。这使打破依赖关系和添加自动化测试成为可能,无需或仅进行最小代码更改。这个库提供了一种创建缝隙类型的方法:对象缝隙。使用对象缝隙,可以在自动化测试中更改受测试的对象,同时保持受测试的类的代码不变。
安装
composer require --dev robvanaarle/php-object-seam ^1
需求
PHP >= 7.0
由于遗留代码通常在较旧的PHP版本上运行,因此此软件包旨在支持尽可能多的PHP版本。
特性
- 调用受保护的和私有方法
- 调用受保护的静态方法
- 重写公共和受保护的类方法
- 重写公共和受保护的静态方法
- 使用自定义构造函数实例化对象
- 捕获和检索公共和受保护的类方法调用
- 捕获和检索公共和受保护的静态方法调用
- 当使用
CreatesObjectSeams
特性时,在PhpStorm中进行自动完成 - 测试框架无关
这允许使用《有效地与遗留代码协作》一书中的以下依赖关系打破技术。
- 子类化并公开
- 子类化并重写
- 公开静态方法
手动创建对象缝隙代码的优势
- 代码更少:大部分所需的代码都是自动生成的
- 更快地编写
- 更明确地表明打破依赖关系的意图:手动创建的对象缝隙代码往往变得模糊
ObjectSeam
可以在测试之前部分构建,并针对特定测试进行更改(甚至构建)
基本用法
在测试类中使用特性 PHPObjectSeam\CreatedObjectSeams
来创建 ObjectSeams
。通常为受测试的对象创建 ObjectSeams
。然后可以对其进行更改,而无需或仅对受测试的类进行少量代码更改,例如调用非公共方法或重写方法行为。创建的 ObjectSeam
是未构造的:原始构造函数,__construct
,尚未调用。这允许设置可以由多个测试重用和定制的 ObjectSeam
。
class FooTest { use PHPObjectSeam\CreatesObjectSeams; public function testBar(): void { $foo = $this->createObjectSeam(Foo::class); // $foo has type Foo&PHPObjectSeam\ObjectSeam // Access seam through seam() to alter the behaviour of the object $foo->seam() ->override('connect', fn ($username, $password) => 'dummy_token') ->customConstruct(function($arg1) { $this->url = 'http://www.dummy.url/' . $arg1; }, 'api/v1/'); // do something with $foo and perform an assertion } }
用法
调用非公共方法
$foo = $this->createObjectSeam(Foo::class); $result = $foo->seam()->call('nonPublicMethod', $arg1, $arg2);
这可以用于“子类化并公开”。
调用受保护的静态方法
$foo = $this->createObjectSeam(Foo::class); $result = $foo->seam()->callStatic('protectedStaticMethod', $arg1, $arg2);
这可以用于“子类化并公开”。
重写公开或受保护的类方法
被重写的类方法将在对象连接类的作用域内执行。
使用闭包重写
$foo = $this->createObjectSeam(Foo::class); $result = $foo->seam()->override('protectedMethod', function(int $arg1) { return $this->otherMethod($arg1) * 5; });
使用结果值重写
$foo = $this->createObjectSeam(Foo::class); $result = $foo->seam()->override('protectedMethod', 42);
这可以用于“子类化和重写”,目的是改变公开或受保护的类方法的行为。
重写公开或受保护的静态方法
被重写的静态方法将在对象连接类的作用域内执行。
使用闭包重写
$foo = $this->createObjectSeam(Foo::class); $result = $foo->seam()->overrideStatic('protectedStaticMethod', function(int $arg1) { return parent::protectedMethod($arg1) * 3; });
使用结果值重写
$foo = $this->createObjectSeam(Foo::class); $result = $foo->seam()->overrideStatic('protectedStaticMethod', 9);
这可以用于“子类化和重写”,目的是改变公开或受保护的静态方法的行为。
使用自定义构造函数实例化对象
$foo = $this->createObjectSeam(Foo::class); $foo->seam()->customConstruct(function($arg1) { $this->url = 'http://www.dummy.url/' . $arg1; }, 'api/v1/');
或者设置自定义构造函数并在之后调用它
// i.e. in the setup of your test $this->foo = $this->createObjectSeam(Foo::class); $this->foo->seam()->setCustomConstructor(function($arg1) { $this->url = 'http://www.dummy.url/' . $arg1; }); // in a specific test case $this->foo->callCustomConstructor('api/v1/');
不使用构造函数并调用所需方法可以实现“公开静态方法”,因此不需要将该方法设置为静态。
$this->foo = $this->createObjectSeam(Foo::class); $this->foo->seam()->call('methodThatDoesNotUseThisKeyword');
调用原始构造函数
通常不需要自定义构造函数,那么就必须调用原始构造函数。
$foo = $this->createObjectSeam(Foo::class); $foo->seam()->call('__construct', 'bar');
或者使用辅助方法 callConstruct()
来实现
$foo = $this->createObjectSeam(Foo::class); $foo->seam()->callConstruct('bar');
捕获和检索公共和受保护的类方法调用
捕获和检索调用允许断言方法已被调用以及调用的参数。
$foo = $this->createObjectSeam(Foo::class); $foo->seam()->captureCalls('publicOrProtectedMethod'); // do something with $foo $foo->publicMethod(); $calls = $foo->seam()->getCapturedCalls('publicOrProtectedMethod'); // assert that $calls contains a certain combination of arguments.
捕获和检索公共和受保护的静态方法调用
捕获和检索静态调用允许断言方法已被调用以及调用的参数。
$foo = $this->createObjectSeam(Foo::class); $foo->seam()->captureStaticCalls('publicOrProtectedStaticMethod'); // do something with $foo $foo::publicMethod(); $calls = $foo->seam()->getCapturedStaticCalls('publicOrProtectedMethod'); // assert that $calls contains a certain combination of arguments.