我使用SimpleTest对一些PHP代码进行单元测试,但我遇到了麻烦。在我对数据库类的测试中,我希望能够为PHP mysql
函数设置期望。在我对mail
函数的包装类的测试中,我想模拟PHP mail
函数。这只是一些例子。
关键是:我不(总是)想要测试我的Mail类是否发送电子邮件,我想测试它如何调用mail
函数。我希望能够控制这些函数返回的内容。我希望能够测试我的数据库类,而不需要数据库,灯具和那么多。
我在测试Ruby代码方面有一些经验,而Test :: Unit和RSpec使得单独测试代码非常容易。我是测试PHP的新手,感觉我测试的次数比我需要的要多得多,以便让我的测试通过。
在SimpleTest或PhpUnit或其他一些测试框架中是否有办法使这成为可能或更容易?
答案 0 :(得分:10)
不是以自动方式。你可以做的是编写代码,使外部依赖包装在从外部传入的对象中。在您的生产环境中,您只需连接真正的适配器,但在测试过程中,您可以将其连接到存根或模拟。
如果您真的坚持,可以使用更改php编程模型的runkit extension,以便您可以在运行时重新定义类和函数。这是一个外部和非标准的扩展,但是,请记住这一点。事实标准是一种手动方法,如上所述。
答案 1 :(得分:1)
Here is an interesting article。作者通过覆盖SUT命名空间(被测试服务)中的方法,提出了一个非常有创意的解决方案,以解决全局php函数的“模拟”问题。
这里的代码来自博客文章中模拟time
函数的示例:
<?php
namespace My\Namespace;
use PHPUnit_Framework_TestCase;
/**
* Override time() in current namespace for testing
*
* @return int
*/
function time()
{
return SomeClassTest::$now ?: \time();
}
class SomeClassTest extends PHPUnit_Framework_TestCase
{
/**
* @var int $now Timestamp that will be returned by time()
*/
public static $now;
/**
* @var SomeClass $someClass Test subject
*/
private $someClass;
/**
* Create test subject before test
*/
protected function setUp()
{
parent::setUp();
$this->someClass = new SomeClass;
}
/**
* Reset custom time after test
*/
protected function tearDown()
{
self::$now = null;
}
/*
* Test cases
*/
public function testOneHourAgoFromNoon()
{
self::$now = strtotime('12:00');
$this->assertEquals('11:00', $this->someClass->oneHourAgo());
}
public function testOneHourAgoFromMidnight()
{
self::$now = strtotime('0:00');
$this->assertEquals('23:00', $this->someClass->oneHourAgo());
}
}
不确定这是否是一个很好的方法,但它肯定运作良好,我认为这里值得一提。可以作为讨论的食物......
答案 2 :(得分:0)
有一个PHPUnit扩展,它在内部使用runkit,并且能够使用调用匹配器,参数约束以及模拟对象可以执行的所有操作。
答案 3 :(得分:0)
在PHP 5.3+环境中,您可以通过黑客攻击命名空间来解决使用runkit
扩展的需要。函数调用的唯一要求是不使用完全限定的命名空间,如\mysql_query()
- 而且它们通常不会。然后,您可以在测试中定义相同的函数,方法是在命名空间中定义测试,PHP将调用您的函数而不是全局函数。我个人使用这种方法来存根time()
函数调用。这是nice example with the mockery framework