zeroplex / aspect-mock
由 Aspects 驱动的实验性 Mocking 框架
Requires
- php: >=7.0.0
- goaop/framework: ^2.2.0
- phpunit/phpunit: > 6.0.0
- symfony/finder: ^2.4|^3.0|^4.0
Requires (Dev)
- codeception/base: ^2.4
- codeception/specify: ~0.3
- codeception/verify: ~0.2
This package is auto-updated.
Last update: 2024-09-27 19:02:04 UTC
README
AspectMock 并非一个普通的 PHP Mocking 框架。凭借面向方面编程(AOP)的强大功能和惊人的 Go-AOP 库,AspectMock 允许您在 PHP 代码中几乎模拟任何内容!
文档 | 测试替身构建器 | 类代理 | 实例代理 | 函数代理
动机
PHP 不是一个为可测试性而设计的语言。真的。您如何模拟 time()
函数以在每次测试调用中产生相同的结果?是否有任何方式可以模拟类的静态方法?您能否在运行时重新定义类方法?像 Ruby 或 JavaScript 这样的动态语言允许我们这样做。这些特性对测试至关重要。AspectMock 来拯救!
每天都会在 PHP 中编写数以千计的未测试代码。在大多数情况下,这些代码实际上并不差,但 PHP 没有提供测试它们的能力。您可能会建议从头开始重写它,遵循测试驱动设计实践,并在可能的情况下使用依赖注入。对于稳定的运行代码,这样做值得吗?嗯,有更好的浪费时间的方式。
使用 AspectMock,您可以几乎对任何面向对象的代码进行单元测试。PHP 与 AOP 结合,吸收了我们长期以来所缺少的动态语言的功能。没有不测试您的代码的借口。您不必从头开始重写它以使其可测试。只需安装 AspectMock 与 PHPUnit 或 Codeception,然后尝试编写一些测试。这真的非常简单!
特性
- 为 静态方法 创建测试替身。
- 为在任何地方调用的 类方法 创建测试替身。
- 动态重新定义方法。
- 简单易记的语法。
代码提案
允许模拟静态方法。
让我们在运行时重新定义静态方法并验证它们的调用。
<?php function testTableName() { $this->assertEquals('users', UserModel::tableName()); $userModel = test::double('UserModel', ['tableName' => 'my_users']); $this->assertEquals('my_users', UserModel::tableName()); $userModel->verifyInvoked('tableName'); } ?>
允许替换类方法。
测试使用 ActiveRecord 模式开发的代码。使用 ActiveRecord 模式听起来像是不好的实践吗?不。但下面的代码在经典单元测试中是不可测试的。
<?php class UserService { function createUserByName($name) { $user = new User; $user->setName($name); $user->save(); } } ?>
没有 AspectMock,您需要将 User
作为显式依赖项引入到类 UserService
中才能进行测试。但让我们保留代码原样。它工作。尽管如此,我们仍然应该测试它以避免回归。
我们不想让 $user->save
方法实际执行,因为它将击中数据库。相反,我们将用模拟对象替换它,并验证它是否被 createUserByName
调用。
<?php function testUserCreate() { $user = test::double('User', ['save' => null])); $service = new UserService; $service->createUserByName('davert'); $this->assertEquals('davert', $user->getName()); $user->verifyInvoked('save'); } ?>
拦截父类方法和魔术方法
<?php // User extends ActiveRecord function testUserCreate() { $AR = test::double('ActiveRecord', ['save' => null])); test::double('User', ['findByNameAndEmail' => new User(['name' => 'jon'])])); $user = User::findByNameAndEmail('jon','jon@coltrane.com'); // magic method $this->assertEquals('jon', $user->getName()); $user->save(['name' => 'miles']); // ActiveRecord->save did not hit database $AR->verifyInvoked('save'); $this->assertEquals('miles', $user->getName()); } ?>
覆盖标准 PHP 函数
<?php namespace demo; test::func('demo', 'time', 'now'); $this->assertEquals('now', time());
非常简单
仅需要 4 个方法来验证方法调用和一个方法来定义测试替身
<?php function testSimpleStubAndMock() { $user = test::double(new User, ['getName' => 'davert']); $this->assertEquals('davert', $user->getName()); $user->verifyInvoked('getName'); $user->verifyInvokedOnce('getName'); $user->verifyNeverInvoked('setName'); $user->verifyInvokedMultipleTimes('setName',1); } ?>
以检查方法 setName
是否用 davert
作为参数调用。
<?php $user->verifyMethodInvoked('setName', ['davert']); ?>
哇!但它是如何工作的呢?
不需要 PECL 扩展。Go! AOP 库通过动态修补自动加载的 PHP 类来完成繁重的工作。通过向每个方法调用引入切入点,Go! 允许拦截几乎所有方法的调用。AspectMock 是一个非常小的框架,仅由 8 个文件组成,利用了 Go! AOP 框架 的功能。查看面向切面编程和 Go! 库本身。
需求
PHP >= 5.6 + Go! AOP 需求
安装
1. 将 aspect-mock 添加到您的 composer.json。
{
"require-dev": {
"codeception/aspect-mock": "*"
}
}
2. 将 AspectMock 与 Go! AOP 一起作为依赖项安装。
php composer.phar update
配置
将 AspectMock\Kernel
类包含在您的测试引导文件中。
使用 Composer 的自动加载器
<?php include __DIR__.'/../vendor/autoload.php'; // composer autoload $kernel = \AspectMock\Kernel::getInstance(); $kernel->init([ 'debug' => true, 'includePaths' => [__DIR__.'/../src'] ]); ?>
如果您的项目使用 Composer 的自动加载器,那么这就是您开始所需的一切。
使用自定义自动加载器
如果您使用自定义自动加载器(例如在 Yii/Yii2 框架中),则应明确指定 AspectMock 以修改它
<?php include __DIR__.'/../vendor/autoload.php'; // composer autoload $kernel = \AspectMock\Kernel::getInstance(); $kernel->init([ 'debug' => true, 'includePaths' => [__DIR__.'/../src'] ]); $kernel->loadFile('YourAutoloader.php'); // path to your autoloader ?>
以这种方式加载您项目的所有自动加载器,如果您不是完全依赖于 Composer。
无自动加载器
如果它仍然不起作用...
在测试之前显式加载所有必需的文件
<?php include __DIR__.'/../vendor/autoload.php'; // composer autoload $kernel = \AspectMock\Kernel::getInstance(); $kernel->init([ 'debug' => true, 'includePaths' => [__DIR__.'/../src'] ]); require 'YourAutoloader.php'; $kernel->loadPhpFiles('/../common'); ?>
自定义
有一些选项您可以自定义设置 AspectMock。它们都在 Go! 框架中定义。如果您在项目中运行 AspectMock 时仍然遇到问题,它们可能会有所帮助。
appDir
定义正在测试的 Web 应用的根目录。根目录之外的所有类都将被 AspectMock 生成的代理类替换。默认情况下,它是包含 composer 的vendor
目录的目录。如果您不使用 Composer 或您有自定义的 composer 的 vendor 目录路径,则应指定 appDircacheDir
是更新源 PHP 文件可以存储的目录。如果没有设置此目录,代理类将在每次运行时构建。否则,所有在测试中使用的 PHP 文件都将更新为方面注入并存储到cacheDir
路径。includePaths
是 Go Aop 应该增强的文件所在的目录。应指向您的应用程序源文件以及框架文件和您使用的任何库。excludePaths
是不应受方面影响的 PHP 文件的路径。您应该排除测试文件以防止拦截。
示例
<?php $kernel = \AspectMock\Kernel::getInstance(); $kernel->init([ 'appDir' => __DIR__ . '/../../', 'cacheDir' => '/tmp/myapp', 'includePaths' => [__DIR__.'/../src'] 'excludePaths' => [__DIR__] // tests dir should be excluded ]); ?>
正确配置 AspectMock 非常重要。否则,它可能不会按预期工作或产生副作用。请确保您包含了需要模拟的所有文件,但同时也排除了您的测试文件和测试框架。
在 PHPUnit 中的使用
在您的 phpunit.xml
配置中使用新创建的 bootstrap
。还禁用 backupGlobals
<phpunit bootstrap="bootstrap.php" backupGlobals="false">
在测试之间清除测试双例注册表。
<?php use AspectMock\Test as test; class UserTest extends \PHPUnit_Framework_TestCase { protected function tearDown() { test::clean(); // remove all registered test doubles } public function testDoubleClass() { $user = test::double('demo\UserModel', ['save' => null]); \demo\UserModel::tableName(); \demo\UserModel::tableName(); $user->verifyInvokedMultipleTimes('tableName',2); } ?>
在 Codeception 中的使用
将 AspectMock\Kernel
包含在 tests/_bootstrap.php
中。我们建议从您的 CodeHelper
类中包含对 test::clean()
的调用
<?php namespace Codeception\Module; class CodeHelper extends \Codeception\Module { function _after(\Codeception\TestCase $test) { \AspectMock\Test::clean(); } } ?>
改进?
肯定有改进的空间。此框架并非旨在做您可能需要的所有事情(见下文说明)。但如果您觉得您需要某个功能,请提交一个 Pull Request。由于代码不多,而且 Go! 库有很好的文档,所以这非常容易。
鸣谢
关注 @codeception 以获取更新。
由 Michael Bodnarchuk 开发。
许可证: MIT。
由 Go! 面向切面框架 支持