web-dev-qa-db-ja.com

PHPUnitモック関数?

別の関数のテストを行うために関数を定義する必要があるという興味深いシナリオがあります。テストする関数は次のようになります。

if (function_exists('foo') && ! function_exists('baz')) {
    /**
     * Baz function
     * 
     * @param integer $n
     * @return integer
     */
    function baz($n)
    {
        return foo() + $n;
    }
}

私がfooの存在を確認しているのは、それが開発者のプロジェクトで定義されているかどうかは不明であり、関数bazfooに依存しているためです。このため、bazを呼び出すことができる場合にのみ、fooを定義します。

唯一の問題は、これまでのところテストを書くことが不可能であったということです。 PHPUnit構成でbootstrapスクリプトを作成して、偽のfoo関数を定義し、Composerオートローダーを必要とするスクリプトはまだfooが定義されていないと見なします。fooはComposerパッケージではないため、私のプロジェクトでは必要ありません。明らかに、Mockeryはこれに対して機能しません私の質問は、PHPUnitの経験が豊富な人がこの問題に遭遇し、解決策を見つけたかどうかです。

ありがとう!

19
samrap

コードを少しリファクタリングしてテストしやすくします。

function conditionalDefine($baseFunctionName, $defineFunctionName)
{
    if(function_exists($baseFunctionName) && ! function_exists($defineFunctionName))
    {
        eval("function $defineFunctionName(\$n) { return $baseFunctionName() + \$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;
}

これで十分です。ただし、このコードをリファクタリングして、関数生成をコードジェネレーターに抽出することもできます。ジェネレータに対してユニットテストを記述して、期待どおりの種類の関数が作成されることを確認できます。

2
Jeremy Giberson

これはうまくいくはずです!

_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);
_

関数の依存性注入のようなものですよ;-)

1

これは、可視性をモックする必要がある状況のようです。これは、クラスのライブラリであるPHPUnitの領域の外にあるものです。そのため、引き続きPHPUnitを使用できますが、目標を達成するためにPHPUnitの外に出なければならない場合があります。

関数のモックを作成するために私が考えることができる唯一の方法は、テストケース内で、\ PHPUnit_Framework_TestCaseを拡張するオブジェクトの上です。

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));
    }


}

2つのファイルを作成する必要がある場合があります。1つはfooあり、もう1つはなし、またはfoo/not fooとbaz/not bazをテストする場合は4つです。これは間違いなく標準装備のオプションであり、通常はお勧めしませんが、あなたの場合は、それが最善の策かもしれません。

1
Katie

これで十分ですか?

to-test.php:

<?php
if (function_exists('foo') && ! function_exists('baz')) {
    /**
     * Baz function
     *
     * @param integer $n
     * @return integer
     */
    function baz($n)
    {
        return foo() + $n;
    }
}

BazTest.php:

<?php

class BazTest extends PHPUnit_Framework_TestCase {
    public function setUp()
    {
        function foo()
        {
            // Appropriate mock goes here
            return 1;
        }

        include __DIR__ . '/to-test.php';
    }

    public function testBaz()
    {
        $this->assertEquals(2, baz(1));
        $this->assertEquals(3, baz(2));
    }
}

テストを実行すると、次の結果が得られます。

PHPUnit 5.4.8 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 54 ms, Memory: 8.00MB

OK (1 test, 2 assertions)
0
Justin McAleer

bootstrapファイルに追加しても機能しません-なぜですか?

関数bazが(A)システムによって正しく作成されることもあれば、(B)それをモックする必要があるためでもありますか?または(C)常にモックする必要がありますか?

  • ケースA:なぜ、コードがその場で散発的に重要な関数を作成するのですか?
  • ケースB:関数は一度だけ登録でき、登録解除または上書きされることはありません。したがって、モックを使用するか、使用しないかのどちらかです。混合は許可されていません。
  • ケースC:常にモックする必要があり、bootstrapファイルに追加する場合、それが定義されます。試行したことに関しては、bootstrap phpunitのファイルが正しく読み込まれていないか、関数名のスペルを間違えています。

Phpunitのブートストラップが正しく構成されていると思いますが、ある程度は、次のようになります。

/tests/phpunit.xml

<phpunit
    bootstrap="phpunit.bootstrap.php"
</phpunit>

/tests/phpunit.bootstrap.php

<?php
require(__DIR__ . "/../bootstrap.php"); // Application startup logic; this is where the function "baz" gets defined, if it exists

if (function_exists('foo') && ! function_exists('baz')) {
    /**
     * Baz function
     * 
     * @param integer $n
     * @return integer
     */
    function baz($n)
    {
        return foo() + $n;
    }
}

テストでオンザフライで関数bazを作成しないでください。 setUp関数内。

Phpunitのテストスイートは同じブートストラップを使用します。したがって、関数bazが定義されているケースと、定義されていない(そしてモックする必要がある)他のケースをテストする必要がある場合は、testsフォルダーを分割する必要があります。 2つの異なるフォルダにあり、それぞれにphpunit.xmlおよびphpunit.bootstrap.phpファイル。例えば。 /tests/with-bazおよび/tests/mock-baz。これらの2つのフォルダーから、テストを個別に実行します。各サブフォルダにphpunitへのシンボリックリンクを作成するだけです(例:/test/with-baz作成ln -s ../../vendor/bin/phpunit if composerがルートにある)両方のシナリオで同じバージョンのphpunitを実行できるようにします。

もちろん、最終的な解決策は、baz関数が定義されている場所を特定し、可能であれば、原因のスクリプトファイルを手動で含めて、正しいロジックが適用されていることを確認することです。

代替

Phpunitの@runInSeparateProcessアノテーションを付け、必要に応じて関数を定義します。

<?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'));
    }
}
0
Kafoso