我目前正在处理的项目包含面向对象和过程PHP代码的混合。所以我有这样的东西:
function doStuff($value)
{
$x = $value + 1;
return $x;
}
class MyClass
{
private $field;
public function setMyValue($amount)
{
$this->field = doStuff($amount) + doStuff(2 * $amount);
}
}
这些依赖项中有一些,但很少(您可以使用一只手计算它们)。但是,我需要为类编写单元测试(使用 PHPUnit),我不知道如何模拟来自过程方面的函数(在本例中为 doStuff
)。据我所知,PHPUnit 中的模拟功能仅适用于类。
我只会在没有任何模拟的情况下这样做,但问题是其中一些函数执行一些 IO 操作;我认为不以某种方式嘲笑他们不是一个好主意。
我该如何解决这个问题?
当您从命名空间调用函数(在全局命名空间中定义)并始终调用它们为非限定函数时,您可以利用 PHP 的命名空间回退策略。
PHP 将回退到全局函数 [...] 如果命名空间函数 [...] 不存在。
这允许您通过在调用方的命名空间中提供函数来创建模拟。
为了让你的生活特别轻松,我把它打包到函数库中 php-mock-phpunit 中,它可以与 PHPUnit 一起使用:
namespace foo;
use phpmock'phpunit'PHPMock;
class BuiltinTest extends 'PHPUnit_Framework_TestCase
{
use PHPMock;
public function testTime()
{
$time = $this->getFunctionMock(__NAMESPACE__, "time");
$time->expects($this->once())->willReturn(3);
$this->assertEquals(3, time());
}
}
我看到的唯一选择是依赖注入,因为您的类想要使用类外部的资源。 因此,这违反了一些封装规则。
我过去的做法是将这些函数放在它们自己的类中并要求/包含它们,并且在设置测试变量时,包含一个具有相同半"模拟"函数的基本文件,该文件返回已知状态。
我的另一种方法是创建一个简单的 UTILITY 类,其中包含所有这些数据函数,然后使用依赖注入和模拟进行测试。
class Utilities
{
function doStuff($value)
{
$x = $value + 1;
return $x;
}
}
class MyClass
{
private $UtilitiesObject;
private $field;
public function setMyValue($amount)
{
// $this->field = doStuff($amount) + doStuff(2 * $amount);
$this->field = $this->UtilitiesObject->doStuff($amount) + $this->UtilitiesObject->doStuff(2 * $amount);
}
}
// Constructor Injection, pass the Utilities object here
public function __construct($Utilities = NULL)
{
if(! is_null($Utilities) )
{
if($Utilities instanceof Utilities)
{
$this->SetUtilities($Utilities);
}
}
}
function SetUtilities(Utilities $Utilities)
{
$this->UtilitiesObject = $Utilities
}
}
测试:
class UtilitiesTest extends PHPUnit_Framework_TestCase
{
// Could also use dataProvider to send different returnValues, and then check with Asserts.
public function testSetMyValue()
{
// Create a mock for the Utilities class,
// only mock the doStuff() method.
$MockUtilities = $this->getMock('Utilities', array('doStuff'));
// Set up the expectation for the doStuff() method
$MockUtilities->expects($this->any())
->method('doStuff')
->will($this->returnValue(1));
// Create Test Object - Pass our Mock as the Utilities
$TestClass = new MyClass($MockUtilities);
// Or
// $TestClass = new MyClass();
// $TestClass->SetUtilitiess($MockUtilities);
// Test doStuff
$amount = 10; // Could be checked with the Mock functions
$this->assertEquals(2, $TestClass->doStuff($amount)); // Mock always returns 1, so 1+1=2
}
}
您可以尝试重新定义"模拟"这些函数。
使用 PHP 库的一个选项 - 拼凑
Patchwork'replace("size", function($x)
{
return "huge";
});
或硬编码解决方案 - runkit 扩展(通过 PECL 安装) -
朗基特
bool runkit_function_redefine ( string $funcname , string $arglist , string $code )