PHPUnit模拟函数?
我有一个有趣的场景,我需要定义一个函数,以便对另一个函数进行测试。我要测试的函数如下所示:PHPUnit模拟函数?,php,unit-testing,mocking,phpunit,Php,Unit Testing,Mocking,Phpunit,我有一个有趣的场景,我需要定义一个函数,以便对另一个函数进行测试。我要测试的函数如下所示: if (function_exists('foo') && ! function_exists('baz')) { /** * Baz function * * @param integer $n * @return integer */ function baz($n) { return foo(
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* @param integer $n
* @return integer
*/
function baz($n)
{
return foo() + $n;
}
}
conditionalDefine('foo', 'bar');
我检查foo
是否存在的原因是,它可能在开发人员的项目中定义,也可能没有定义,而baz
函数依赖于foo
。因此,如果可以调用foo
,我只希望定义baz
唯一的问题是,到目前为止,还无法为其编写测试。我尝试在PHPUnit配置中创建一个引导脚本,该脚本将定义一个伪foo
函数,然后需要Composer autoloader,但我的主脚本仍然认为没有定义foo
foo
不是Composer软件包,因此我的项目不需要它。显然,嘲弄也不会起作用。我的问题是,是否有任何有PHPUnit经验的人遇到过这个问题并找到了解决方案
谢谢 这应该行得通
use MyProject\baz;
class YourTestCase
{
/** @var callable **/
protected $mockFoo;
/** @var callable **/
protected $fakeFoo;
public function setUp()
{
if (function_exists('foo')) {
$this->mockFoo = function($foosParams) {
foo($foosParams);
// Extra Stuff, as needed to make the test function right.
};
}
$this->fakeFoo = function($foosParams) {
// Completely mock out foo.
};
}
public function testBazWithRealFoo()
{
if (!$this->mockFoo) {
$this->markTestIncomplete('This system does not have the "\Foo" function.');
}
$actualResults = baz($n, $this->mockFoo);
$this->assertEquals('...', $actualResults);
}
public function testBazWithMyFoo()
{
$actualResults = baz($n, $this->fakeFoo);
$this->assertEquals('...', $actualResults);
}
}
然后修改现有代码:
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* @param integer $n
* @return integer
*/
function baz($n)
{
return foo() + $n;
}
}
namespace MyProject
{
function baz($bazParams, callable $foo = '\foo')
{
return $foo() + $bazParams;
}
}
然后,您需要调用以下命令,而不是调用baz($n)
:
use MyProject\baz;
baz($bazParams);
这就像函数的依赖注入,yo;-) 这足够了吗
to-test.php:
<?php
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* @param integer $n
* @return integer
*/
function baz($n)
{
return foo() + $n;
}
}
在引导文件中添加它不起作用-为什么 是不是因为有时函数
baz
是(A)由系统正确创建的,有时(B)需要模拟它?或者(C)你总是需要嘲笑它吗
- 案例A:为什么代码会偶尔动态地创建一个重要函数
- 案例B:一个函数只能注册一次,不能注销或覆盖。因此,你要么选择模仿,要么不选择。不允许混合
- 案例C:如果您总是需要模拟它,并将其添加到引导文件中,那么它将被定义。关于您尝试过的内容,可能是phpunit的引导文件没有正确加载,或者是函数名拼写错误
/tests/phpunit.xml
:
<phpunit
bootstrap="phpunit.bootstrap.php"
</phpunit>
不要在测试中动态创建函数baz
,例如在设置中创建函数
phpunit中的测试套件使用相同的引导程序。因此,如果您需要测试定义了函数baz
的案例和未定义函数的其他案例(并且需要对其进行模拟),则需要将测试
文件夹拆分为两个不同的文件夹,例如,在两个不同的文件夹中,每个文件夹都有phpunit.xml
和phpunit.bootstrap.php
文件。例如,/tests/with baz
和/tests/mock baz
。从这两个文件夹中,分别运行测试。只需在每个子文件夹中创建指向phpunit
的符号链接(例如,从/test/with baz
createln-s.././vendor/bin/phpunit
,如果composer在根目录中),以确保在两种情况下运行相同版本的phpunit
当然,最终的解决方案是找出baz
函数的定义位置,并在可能的情况下手动包含罪犯脚本文件,以确保应用正确的逻辑
备选方案
使用phpunit的@runinsepareprocess
注释并根据需要定义函数
<?php
class SomeTest extends \PHPUnit_Framework_TestCase
{
/**
* @runInSeparateProcess
*/
public function testOne()
{
if (false === function_exists('baz')) {
function baz() {
return 42;
}
}
$this->assertSame(42, baz());
}
public function testTwo()
{
$this->assertFalse(function_exists('baz'));
}
}
这似乎是一种需要模拟可见性的情况,这显然超出了PHPUnit的范畴,PHPUnit是一个类库。因此,您仍然可以使用PHPUnit,但可能需要跳出它来实现您的目标
我能想到的创建函数模拟的唯一方法是在测试用例内部,在扩展\PHPUnit\u Framework\u测试用例的对象之上
function foo() {
return 1;
}
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* @param integer $n
* @return integer
*/
function baz($n)
{
return foo() + $n;
}
}
class TestBaz extends \PHPUnit_Framework_TestCase
{
public function test_it() {
$this->assertSame(4,baz(3));
}
}
如果要测试foo/notfoo和baz/notbaz,可能需要创建两个文件,一个有foo,一个没有,或者四个。这绝对是一个开箱即用的选项,我通常不会推荐,但在您的情况下,这可能是您的最佳选择。首先对代码进行轻微的重构,使其更易于测试
function conditionalDefine($baseFunctionName, $defineFunctionName)
{
if(function_exists($baseFunctionName) && ! function_exists($defineFunctionName))
{
eval("function $defineFunctionName(\$n) { return $baseFunctionName() + \$n; }");
}
}
那么就这样称呼它:
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* @param integer $n
* @return integer
*/
function baz($n)
{
return foo() + $n;
}
}
conditionalDefine('foo', 'bar');
您的PHPUnit测试类将包含以下测试:
public function testFunctionIsDefined()
{
$baseName = $this->mockBaseFunction(3);
$expectedName = uniqid('testDefinedFunc');
conditionalDefine($baseName, $expectedName);
$this->assertTrue(function_exists($expectedName));
$this->assertEquals(5, $expectedName(2));
}
public function testFunctionIsNotDefinedBecauseItExists()
{
$baseName = $this->mockBaseFunction(3);
$expectedName = $this->mockBaseFunction($value = 'predefined');
conditionalDefine($base, $expectedName);
// these are optional, you can't override a func in PHP
// so all that is necessary is a call to conditionalDefine and if it doesn't
// error, you're in the clear
$this->assertTrue(function_exists($expectedName));
$this->assertEquals($value, $expectedName());
}
public function testFunctionIsNotDefinedBecauseBaseFunctionDoesNotExists()
{
$baseName = uniqid('testBaseFunc');
$expectedName = uniqid('testDefinedFunc');
conditionalDefine($base, $expectedName);
$this->assertFalse(function_exists($expectedName));
}
protected function mockBaseFunction($returnValue)
{
$name = uniqid('testBaseFunc');
eval("function $name() { return '$value'; }");
return $name;
}
这就足够满足你的要求了。但是,您可以进一步重构此代码,将函数生成提取到代码生成器中。这就是您可以针对生成器编写的单元测试,以确保它创建您期望的函数类型 为测试用函数命名空间如何?我不是百分之百地确定测试函数的实现是什么,但是:问题是我需要定义一个不存在的函数,以便测试依赖它的函数@unatasanatarai。我不需要更改现有函数的实现。如果我可以在您的示例中添加一个名称空间
,我会给您一个答案。否则,请选择类似runkit.Grrr的内容。php mock phpunit看起来很棒,但不幸的是,我需要模拟的函数位于根命名空间中(来自WordPress插件;是的,不要问我为什么WordPress-__;-),所以我将看看Runkit,你根本无法模拟这种过程代码。我要么将这些东西包装在一个可模仿的类中,要么使用(环境)定义,比如C风格的头保护。不幸的是,没有。我正在使用Composer,它通过自动加载处理文件包含。也许你应该展示一些真实的代码。。。您包含的是所有全局函数,因此我不清楚自动加载是否适用于此特定函数baz。A和B不适用。我不关心foo
的功能,只需要它返回一个静态值(foo不是我的函数,所以我的测试不关心它如何工作)。我的引导文件看起来与您的示例相同,但在我正在测试的代码中似乎仍然没有定义该函数。在这一点上,PHPUnit的工作方式可能存在问题
public function testFunctionIsDefined()
{
$baseName = $this->mockBaseFunction(3);
$expectedName = uniqid('testDefinedFunc');
conditionalDefine($baseName, $expectedName);
$this->assertTrue(function_exists($expectedName));
$this->assertEquals(5, $expectedName(2));
}
public function testFunctionIsNotDefinedBecauseItExists()
{
$baseName = $this->mockBaseFunction(3);
$expectedName = $this->mockBaseFunction($value = 'predefined');
conditionalDefine($base, $expectedName);
// these are optional, you can't override a func in PHP
// so all that is necessary is a call to conditionalDefine and if it doesn't
// error, you're in the clear
$this->assertTrue(function_exists($expectedName));
$this->assertEquals($value, $expectedName());
}
public function testFunctionIsNotDefinedBecauseBaseFunctionDoesNotExists()
{
$baseName = uniqid('testBaseFunc');
$expectedName = uniqid('testDefinedFunc');
conditionalDefine($base, $expectedName);
$this->assertFalse(function_exists($expectedName));
}
protected function mockBaseFunction($returnValue)
{
$name = uniqid('testBaseFunc');
eval("function $name() { return '$value'; }");
return $name;
}