myplanetdigital / function_mock
用于帮助为 PHP 脚本单元测试模拟函数的框架
Requires
- php: >=5.3.3
This package is not auto-updated.
Last update: 2022-02-01 12:32:11 UTC
README
通用的 PHP 框架,帮助生成在特定文件中未定义的函数存根。与 PHPUnit 结合使用进行单元测试。请参阅 此文章 了解更多有关模拟和存根的信息。
背景
该框架起源于编写 Drupal CMS 模块的 PHPUnit 测试的愿望。由于 Drupal(7 及之前版本)不是面向对象的,它主要使用函数,并且经常调用其他假定由 Drupal 导入的函数。其中一些访问数据库,这使得隔离变得困难。
由于 Drupal 中的所有内容都包含在函数中,而 PHPUnit 无法直接模拟函数(它只能模拟类),因此创建了此框架,允许您为函数生成实际的“模拟”以进行存根。这样做允许您单独测试 .module 文件,例如,而不必包含其他依赖文件。
创建模拟
要创建模拟,请使用 FunctionMock::createMockFunctionDefinition($functionName)
并指定要模拟的函数名称
e.g. FunctionMock::createMockFunctionDefinition('external_method');
实际上,这会在底层创建并评估一个名为 external_method()
的新函数。其实现在允许其返回值被存根为任何您想要的内容,通过 FunctionMock::stub($functionName, $stubValue)
方法。
存根模拟
要存根模拟的返回值,请使用 FunctionMock::stub(...)
方法。
有两种版本,一种用于设置方法在通常情况下应返回的内容:FunctionMock::stub($functionName, $stubValue)
e.g. FunctionMock::stub('external_method', 'abc');
然后,如果执行以下操作
$result = external_method();
$result
返回 'abc'
。
另一种版本接受一个数组作为第三个参数,指定精确匹配参数的返回值:FunctionMock::stub($functionName, $stubValue, $paramList)
e.g. FunctionMock::stub('external_method', 'def', array('param1', 'param2'));
现在,如果调用以下内容
$result = external_method('param1', 'param2');
$result
的值将是 'def'
。
如果您想重置所有存根的值,请调用 FunctionMock::resetStubs()
,这将清除每个模拟的所有存根值。
整合一切 - PHPUnit 示例
让我们以 Drupal 的 Block 模块为例
/**
* Implements hook_block_info().
*/
function block_block_info() {
$blocks = array();
$result = db_query('SELECT bid, info FROM {block_custom} ORDER BY info');
foreach ($result as $block) {
$blocks[$block->bid]['info'] = $block->info;
// Not worth caching.
$blocks[$block->bid]['cache'] = DRUPAL_NO_CACHE;
}
return $blocks;
}
请注意,没有同时测试 db_query()
(它访问数据库)的情况下,无法轻松测试 block_block_info()
。
单元测试的关键是假设所有依赖的类和函数都已经正常工作,因此您将假设 db_query()
工作正常,模拟它,因为它是外部函数,并相应地存根其返回值。
假设您已安装 PHPUnit,您可以编写如下测试用例
<?php
require_once '../modules/block/block.module';
require_once '../sites/all/libraries/function_mock/function_mock.php';
class BlockTest extends PHPUnit_Framework_TestCase
{
public function testBlockBlockInfo()
{
// Setup initial test variables.
define('DRUPAL_NO_CACHE', -5);
$blockInfo = array();
$blockInfo[] = (object) array('bid' => 12345, 'info' => 'Block Info 1');
$blockInfo[] = (object) array('bid' => 23456, 'info' => 'Block Info 2');
FunctionMock::createMockFunctionDefinition('db_query');
FunctionMock::stub('db_query', $blockInfo);
// Exercise the block_block_info() method.
$result = block_block_info();
// Verify it worked.
$this->assertEquals('Block Info 1', $result[12345]['info']);
$this->assertEquals(DRUPAL_NO_CACHE, $result[12345]['cache']);
$this->assertEquals('Block Info 2', $result[23456]['info']);
$this->assertEquals(DRUPAL_NO_CACHE, $result[23456]['cache']);
}
}
?>
自动生成模拟函数
虽然你可以为需要的每个函数生成一个模拟函数,但你也可以使用function_mock自动根据你要测试的文件生成所有可能的函数。为此,使用FunctionMock::generateMockFunctions($srcFileList)
并提供你想要测试的所有源文件列表。此方法将在$srcFileList
的作用域内搜索,并确定哪些函数没有实现,为每个函数创建模拟。
以下是如何使用它的示例
<?php
require_once '../modules/block/block.module';
require_once '../sites/all/libraries/function_mock/function_mock.php';
class BlockTest extends PHPUnit_Framework_TestCase
{
public function __construct()
{
// Generate all functions that need mocks from the block module, based on what
// hasn't been defined yet.
FunctionMock::generateMockFunctions(array('../modules/block/block.module'));
}
public function testBlockBlockInfo()
{
// Setup initial test variables.
define('DRUPAL_NO_CACHE', -5);
$blockInfo = array();
$blockInfo[] = (object) array('bid' => 12345, 'info' => 'Block Info 1');
$blockInfo[] = (object) array('bid' => 23456, 'info' => 'Block Info 2');
FunctionMock::stub('db_query', $blockInfo);
// Exercise the block_block_info() method.
$result = block_block_info();
// Verify it worked.
$this->assertEquals('Block Info 1', $result[12345]['info']);
$this->assertEquals(DRUPAL_NO_CACHE, $result[12345]['cache']);
$this->assertEquals('Block Info 2', $result[23456]['info']);
$this->assertEquals(DRUPAL_NO_CACHE, $result[23456]['cache']);
}
public function __destruct()
{
// Clean up the stubbed values.
FunctionMock::resetStubs();
}
}
?>
待办事项
请随时记录有关此框架的问题或建议。到目前为止,以下是一些已知的问题
- 在创建可能已经存在的方法的模拟/存根时,需要更强的异常处理。目前创建的模拟没有跟踪,因此很难确定哪些是系统函数,哪些是模拟函数。
- 在function_mock中进行一些一般性的清理工作,以分离其一些功能。
- 如果使用Drupal,或直接使用PHP,说明框架代码应该放置的位置的更多文档。
- 错误场景的文档,以及一些额外的测试。
许可证
本项目的许可协议为MIT许可证。