Php 在不相关的类中进行静态方法调用的替代方法是什么?

Php 在不相关的类中进行静态方法调用的替代方法是什么?,php,oop,architecture,phpunit,static-libraries,Php,Oop,Architecture,Phpunit,Static Libraries,我正在用PHP中的许多实用程序库对一个大型代码库进行单元测试和重构 有很多这样的库,到处都是方便的方法。大多数静态库与配置文件交互(通过另一个静态类)。这里有一个很好的例子: class core_lang { public static function set_timezone() { if(cfg::exists('time_zone')) { putenv("TZ=".cfg::get('time_zone'));

我正在用PHP中的许多实用程序库对一个大型代码库进行单元测试和重构

有很多这样的库,到处都是方便的方法。大多数静态库与配置文件交互(通过另一个静态类)。这里有一个很好的例子:

class core_lang {
    public static function set_timezone()
    {
        if(cfg::exists('time_zone')) {
            putenv("TZ=".cfg::get('time_zone'));
        }
    }
}
当然,还有另外一层更具体的库,在另一个函数中调用
core\u lang::set\u timezone()

这使得这些类很难编写单元测试,至少在PHPUnit中是这样,因为您只能模拟。。。基本上是一个层次


我订购了《有效地使用遗留代码》这本书,但是为了可测试性,开始重构和管理这类代码有哪些策略?

减少耦合的最重要原则是依赖注入。有许多方法可以实际实现它,但基本概念是相同的:

不要将依赖项硬编码到代码中,而是请求它们。

在您的特定示例中,一种正确执行此操作的方法如下:

您定义了一个接口(现在我们称之为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

你可以阅读更多关于这个概念的文章。

PHPUnit的作者有一篇关于。它通常建议与其他答案相同,也就是说,因为它们是,但将代码更改为使用依赖项注入

但是,PHPUnit允许模拟和存根静态方法调用

BlogPost中关于存根静态方法的示例: 它还允许通过

注意:

BlogPost中用于存根硬编码依赖项的示例:
以这种方式测试代码并不意味着可以使用硬编码的静态依赖项。您仍然应该重构代码以使用依赖项注入。但是为了进行重构,必须首先进行单元测试。因此,这使您能够真正开始改进遗留代码。

既然我提供了一个我试图重构的代码示例,您认为您可以提供一个修复该代码的简单代码示例吗?还要注意,我将您原来的
set_timezone()
方法改为不再是静态的,因为如果保持这种方式,那么使用
core\u lang
的其他类将以与
cfg
耦合到
core\u lang
相同的方式耦合到它。总而言之,您还应该为
core\u lang
定义一个接口,以便在需要测试时能够模拟(或切换)
core\u lang
的实现。
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()
        );
    }
}
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());
    }
}