Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/251.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
PHPUnit模拟函数?_Php_Unit Testing_Mocking_Phpunit - Fatal编程技术网

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的引导文件没有正确加载,或者是函数名拼写错误
我确信您已经正确配置了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
create
ln-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;
}