我正在使用PHP中的许多实用程序库对大型代码库进行单元测试和重构。
有很多这样的图书馆,充满了遍布整个网站的便捷方法。大多数静态库与配置文件交互(通过另一个静态类)。这是一个很好的例子:
class core_lang {
public static function set_timezone()
{
if(cfg::exists('time_zone')) {
putenv("TZ=".cfg::get('time_zone'));
}
}
}
然后,当然,在另一个函数中调用core_lang:: set_timezone()
的地方还有另一层更具体的库。
这使得这些类非常难以编写单元测试,至少在PHPUnit中,因为你只能模拟...基本上是一个级别。
我订购了“有效使用旧版代码”一书,但是有哪些策略可以开始重构和管理这类代码以实现可测试性?
答案 0 :(得分:3)
减少耦合的最重要原则是依赖注入。有很多方法可以实际实现它,但基本概念是相同的:
不要将依赖项硬编码到您的代码中,而是要求它们。
在您的特定示例中,执行此操作的一种方法如下:
您定义了一个接口(我们现在将其称为ExistenceChecker),它公开了一个名为“exists()”的方法。在生产代码中,您创建了一个实际实现该方法的类(让我们称之为ConcreteExistenceChecker),并在core_lang的构造函数中请求ExistenceChecker对象。这样,您可以在单元测试代码时传递实现此接口的存根对象(但具有简单的简单实现)。从现在开始,您不必依赖于具体的类,只需要一个接口,这会引入更少的耦合。
让我用一些代码演示它:
interface ExistenceChecker {
public function exists($timezone);
}
class ConcreteExistenceChecker implements ExistenceChecker {
public function exists($timezone) {
// do something and return a value
}
}
class ExistenceCheckerStub implements ExistenceChecker {
public function exists($timezone) {
return true; // trivial implementation for testing purposes
}
}
class core_lang {
public function set_timezone(ExistenceChecker $ec)
{
if($ec->exists('time_zone')) {
putenv("TZ=".cfg::get('time_zone'));
}
}
}
生产代码:
// setting timezone
$cl = new core_lang();
$cl->set_timezone(new ConcreteExistenceChecker()); // this will do the real work
测试代码:
// setting timezone
$cl = new core_lang();
$cl->set_timezone(new ExistenceCheckerStub()); // this will do the mocked stuff
您可以详细了解此概念here。
答案 1 :(得分:3)
PHPUnit的作者有一篇关于Stubbing and Mocking Static Methods的博客文章。它通常建议与其他答案相同,即dont use statics,因为它们是death to testability,但更改代码以使用依赖注入。
但是,PHPUnit 确实允许模拟和存根静态方法调用。
class FooTest extends PHPUnit_Framework_TestCase
{
public function testDoSomething()
{
$class = $this->getMockClass(
'Foo', /* name of class to mock */
array('helper') /* list of methods to mock */
);
$class::staticExpects($this->any())
->method('helper')
->will($this->returnValue('bar'));
$this->assertEquals(
'bar',
$class::doSomething()
);
}
}
并且还允许Stubbing HardCoded Dependencies通过Test Helpers extension。
注意: the Test-Helper extension is superseded https://github.com/krakjoe/uopz
class FooTest extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
$this->getMock(
'Bar', /* name of class to mock */
array('doSomethingElse'), /* list of methods to mock */
array(), /* constructor arguments */
'BarMock' /* name for mocked class */
);
set_new_overload(array($this, 'newCallback'));
}
protected function tearDown()
{
unset_new_overload();
}
protected function newCallback($className)
{
switch ($className) {
case 'Bar': return 'BarMock';
default: return $className;
}
}
public function testDoSomething()
{
$foo = new Foo;
$this->assertTrue($foo->doSomething());
}
}
以这种方式测试代码并不意味着使用硬编码的静态依赖项是很好的。您仍然应该重构代码以使用依赖注入。但是为了重构你必须首先拥有UnitTests。因此,这使您能够真正开始改进遗留代码。