別の関数のテストを行うために関数を定義する必要があるという興味深いシナリオがあります。テストする関数は次のようになります。
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* @param integer $n
* @return integer
*/
function baz($n)
{
return foo() + $n;
}
}
私がfoo
の存在を確認しているのは、それが開発者のプロジェクトで定義されているかどうかは不明であり、関数baz
がfoo
に依存しているためです。このため、baz
を呼び出すことができる場合にのみ、foo
を定義します。
唯一の問題は、これまでのところテストを書くことが不可能であったということです。 PHPUnit構成でbootstrapスクリプトを作成して、偽のfoo
関数を定義し、Composerオートローダーを必要とするスクリプトはまだfoo
が定義されていないと見なします。foo
はComposerパッケージではないため、私のプロジェクトでは必要ありません。明らかに、Mockeryはこれに対して機能しません私の質問は、PHPUnitの経験が豊富な人がこの問題に遭遇し、解決策を見つけたかどうかです。
ありがとう!
コードを少しリファクタリングしてテストしやすくします。
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;
}
これで十分です。ただし、このコードをリファクタリングして、関数生成をコードジェネレーターに抽出することもできます。ジェネレータに対してユニットテストを記述して、期待どおりの種類の関数が作成されることを確認できます。
これはうまくいくはずです!
_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);
_
関数の依存性注入のようなものですよ;-)
これは、可視性をモックする必要がある状況のようです。これは、クラスのライブラリである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つです。これは間違いなく標準装備のオプションであり、通常はお勧めしませんが、あなたの場合は、それが最善の策かもしれません。
これで十分ですか?
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)
bootstrapファイルに追加しても機能しません-なぜですか?
関数baz
が(A)システムによって正しく作成されることもあれば、(B)それをモックする必要があるためでもありますか?または(C)常にモックする必要がありますか?
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'));
}
}