sebastian/de-legacy-fy

此软件包已被废弃且不再维护。未建议替代软件包。

处理旧版PHP代码的工具

2.0.0 2017-05-15 06:46 UTC

This package is not auto-updated.

Last update: 2020-04-07 07:52:41 UTC


README

遗留代码是没有测试的代码。多年来,我帮助许多团队将(单元)测试引入到遗留代码库中,使其逐步变得更加现代化。

de-legacy-fy 命令行工具是将处理遗留PHP应用程序中证明有效的概念和想法放入代码,并尽可能使其可重用的尝试。

安装

PHP存档(PHAR)

获取 de-legacy-fy 最简单的方法是下载一个包含所有依赖项的 PHP存档(PHAR) 文件

$ wget https://phar.phpunit.de/de-legacy-fy.phar
$ chmod +x de-legacy-fy.phar
$ mv de-legacy-fy.phar /usr/local/bin/de-legacy-fy

当然,下载后您也可以立即使用PHAR

$ wget https://phar.phpunit.de/de-legacy-fy.phar
$ php de-legacy-fy.phar

Composer

您可以使用 Composer 将此工具添加为本地、项目级、开发时依赖项

$ composer require --dev sebastian/de-legacy-fy

然后,您可以使用 vendor/bin/de-legacy-fy 可执行文件调用它。

使用示例

使用执行跟踪数据生成特征测试

特征测试是尝试将现有行为锁定到未经测试或未记录的系统中的尝试。它们在 Michael Feathers 的书籍 "Working Effectively With Legacy Code" 等地方简要描述。

我们可以使用 执行跟踪 数据自动生成一个用于 PHPUnit 的数据提供者的数据提供者,这些数据我们可以使用 Xdebug 收集。

考虑以下相当牵强的简单示例

<?php
function add($a, $b)
{
    return $a + $b;
}

add(1, 2);

以下命令使用 Xdebug 的执行跟踪启用并配置为发出包含参数和返回值的可机器读取的输出,执行上述 PHP 脚本

$ php -d xdebug.auto_trace=1 -d xdebug.trace_format=1 -d xdebug.collect_params=5 -d xdebug.collect_return=1 test.php

您可以在以下部分看到 Xdebug 收集的执行跟踪数据

$ cat /tmp/trace.4251619279.xt
Version: 2.5.3
File format: 4
TRACE START [2014-06-27 10:40:40]
1	0	0	0.000282	279896	{main}	1		/home/sb/test.php	0	0
2	1	0	0.000371	280136	add	1		/home/sb/test.php	7	2	aToxOw==	aToyOw==
2	1	1	0.000440	280256
2	1	R			aTozOw==
1	0	1	0.000470	280016
1	0	R			aToxOw==
            0.000648	8488
TRACE END   [2014-06-27 10:40:40]

de-legacy-fy 的 generate-characterization-test 命令可以自动生成一个用于 PHPUnit 的数据提供者方法

$ de-legacy-fy generate-characterization-test add /tmp/trace.4251619279.xt CharacterizationTest CharacterizationTest.php
de-legacy-fy 2.0.0 by Sebastian Bergmann.

Generated class "CharacterizationTest" in file "CharacterizationTest.php"

对于 add() 函数的每次调用(de-legacy-fy 命令的第一个参数),数据提供者将产生一个数据集,其中包含传递给函数的参数以及返回的结果。

第二个参数(例如上述示例中的/tmp/trace.4251619279.xt)指向由Xdebug生成的执行跟踪数据文件。第三个和第四个参数用于指定测试类的名称以及要写入源代码的文件。

在这里,您可以看到为我们的示例生成的代码

<?php
use PHPUnit\Framework\TestCase;

class CharacterizationTest extends TestCase
{
    /**
     * @return array
     */
    public function provider()
    {
        return [
            [$this->decode('aTozOw=='), $this->decode('aToxOw=='), $this->decode('aToyOw==')]
        ];
    }

    /**
     * @param string $serializedValue
     *
     * @return mixed
     */
    private function decode($serializedValue)
    {
        return unserialize(base64_decode($serializedValue));
    }
}

为了实现特征测试,我们下面需要做的是

<?php
use PHPUnit\Framework\TestCase;

class CharacterizationTest extends TestCase
{
    /**
     * @dataProvider provider
     */
    public function testAddFunctionWorksLikeItUsedTo($expected, $a, $b)
    {
        $this->assertEquals($expected, add($a, $b));
    }

    /**
     * @return array
     */
    public function provider()
    {
        return [
            [$this->decode('aTozOw=='), $this->decode('aToxOw=='), $this->decode('aToyOw==')]
        ];
    }

    /**
     * @param string $serializedValue
     *
     * @return mixed
     */
    private function decode($serializedValue)
    {
        return unserialize(base64_decode($serializedValue));
    }
}

包装静态API类

静态方法是可测试性的杀手。对于遗留代码库来说,使用全局状态和静态方法是典型的。有时存在只包含静态方法的“库类”

<?php
class Library
{
    public static function doSomething($a, $b)
    {
        // ...
    }
}

静态方法的问题不在于静态方法本身难以测试。问题在于使用静态方法的代码与静态方法紧密耦合,这使得在没有执行静态方法代码的情况下无法进行测试。

在下面的代码中,Library类是Processorprocess()方法的隐式依赖项。这是隐式的,因为从方法的API中看不出来它依赖于Library。此外,我们无法将process()方法与doSomething()方法分开进行测试。

<?php
class Processor
{
    public function process()
    {
        // ...

        Library::doSomething('...', '...');

        // ...
    }
}

de-legacy-fywrap-static-api命令可以自动为静态API类,如Library,生成包装类

$ de-legacy-fy wrap-static-api Library Library.php
de-legacy-fy 2.0.0 by Sebastian Bergmann.

Generated class "LibraryWrapper" in file "LibraryWrapper.php"
<?php
/**
 * Automatically generated wrapper class for Library
 * @see Library
 */
class LibraryWrapper
{
    /**
     * @see Library::doSomething
     */
    public function doSomething($a, $b)
    {
        return Library::doSomething($a, $b);
    }
}

现在,我们可以将LibraryWrapper作为Processor的依赖项

<?php
class Processor
{
    private $library;

    public function __construct(LibraryWrapper $library)
    {
        $this->library = $library;
    }

    public function process()
    {
        // ...

        $this->library->doSomething('...', '...');

        // ...
    }
}

Processor类不再直接使用遗留的Library类,并且可以独立于它进行测试(因为我们现在可以模拟或存根LibraryWrapper类)。

使用抽象分支的概念,我们现在可以编写使用LibraryWrapper类的新代码,并将旧代码从Library迁移到LibraryWrapper。最终,我们可以在LibraryWrapper类中重新实现Library的功能。一旦没有代码依赖于Library,我们就可以删除Library并将LibraryWrapper重命名为Library