myplanetdigital/function_mock

此包已被弃用且不再维护。未建议替代包。

用于帮助为 PHP 脚本单元测试模拟函数的框架

dev-master 2014-03-07 18:56 UTC

This package is not auto-updated.

Last update: 2022-02-01 12:32:11 UTC


README

Build Status

通用的 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许可证。