我有一个有趣的场景,我需要定义一个函数,以便为另一个函数进行测试。我想测试的功能看起来像这样:
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配置中创建一个引导脚本,该脚本将定义假foo
函数,然后需要Composer自动加载器,但我的主脚本仍然认为foo
未定义。 foo
不是Composer包,我的项目不能要求它。显然,Mockery也不适用于此。我的问题是,如果有更多经验丰富的PHPUnit遇到了这个问题并找到了解决方案。
谢谢!
答案 0 :(得分:2)
从代码的轻微重构开始,使其更易于测试。
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;
}
这足以满足您的要求。但是,您可以进一步重构此代码,将函数生成提取到代码生成器中。那就是你可以编写针对生成器的单元测试,以确保它创建了你期望的那种功能。
答案 1 :(得分:1)
这应该有效!
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);
就像函数的依赖注入一样,哟; - )
答案 2 :(得分:1)
这似乎是一种需要模拟可见性的情况,这绝对是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));
}
}
您可能需要创建两个文件,一个使用foo,一个不使用,或者如果要测试foo / not foo和baz / not baz则需要四个文件。这绝对是一个开箱即用的选项,我通常不会推荐,但在你的情况下,这可能是你最好的选择。
答案 3 :(得分:0)
这还够吗?
到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)
答案 4 :(得分:0)
在bootstrap文件中添加它不起作用 - 为什么?
是因为有时函数baz
是(A)系统正确创建的,有时(B)你需要嘲笑它吗?或者(C)你总是需要嘲笑它吗?
我确定你已经正确配置了你的phpunit bootstrapping,但是为了更好的衡量,它看起来像下面这样:
/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
文件夹,例如在两个不同的文件夹中,每个文件夹都包含phpunit.xml
和phpunit.bootstrap.php
个文件。例如。 /tests/with-baz
和/tests/mock-baz
。从这两个文件夹中,分别运行测试。只需为每个子文件夹中的phpunit
创建符号链接(例如,如果作曲家在根目录中,则从/test/with-baz
创建ln -s ../../vendor/bin/phpunit
),以确保在两种情况下都运行相同版本的phpunit。
当然,最终的解决方案是找出baz
函数的定义位置,并手动包含罪魁祸首脚本文件(如果可能的话),以确保正在应用正确的逻辑。
<强>替代强>
使用phpunit&#39; @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'));
}
}