第 12 章 骨架(Skeleton)生成器

PHPUnit 骨架生成器(Skeleton Generator)是用来从成品代码类中生成骨架测试类(或反之)的工具。可以用如下命令安装:

pear install phpunit/PHPUnit_SkeletonGenerator

生成测试用例类的骨架

为已存在的代码编写测试时,不得不反复写下类似这样的代码片段:

public function testMethod()
{
}

PHPUnit 骨架生成器能够分析现有类的代码并帮你生成测试用例类的骨架。

例 12.1: Calculator 类

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


下面这个例子展示了如何为名为 Calculator 的类(参见例 12.1)生成骨架测试类。

phpunit-skelgen --test Calculator
PHPUnit Skeleton Generator 1.0.0 by Sebastian Bergmann.

Wrote skeleton for "CalculatorTest" to "/home/sb/CalculatorTest.php".

原始类中的每一个方法在生成的测试用例类中都会有一个对应的标为不完整的测试用例(参见第 7 章)。

使用了命名空间的类与骨架生成器

如果打算用骨架生成器来为声明在命名空间中的类生成代码,必须提供和其所在的源文件路径一致的类限定名称。

例如,对于声明在 project 命名空间中的 Calculator 类,需要像这样调用骨架生成器:

phpunit-skelgen --test -- "project\Calculator" Calculator.php
PHPUnit Skeleton Generator 1.0.0 by Sebastian Bergmann.

Wrote skeleton for "project\CalculatorTest" to "/home/sb/CalculatorTest.php".

下面是运行生成出来的测试用例类得到的结果:

phpunit --bootstrap Calculator.php --verbose CalculatorTest
PHPUnit 3.9.0 by Sebastian Bergmann.

I

Time: 0 seconds, Memory: 3.50Mb

There was 1 incomplete test:

1) CalculatorTest::testAdd
This test has not been implemented yet.

/home/sb/CalculatorTest.php:38
OK, but incomplete or skipped tests!
Tests: 1, Assertions: 0, Incomplete: 1.

可以在方法的文档注释块中使用 @assert 标注来让生成器自动生成简单却有意义的测试而非不完整的测试用例。例 12.2展示了一个范例:

例 12.2: 带有 @assert 标注的 Calculator 类

<?php
class Calculator
{
    /**
     * @assert (0, 0) == 0
     * @assert (0, 1) == 1
     * @assert (1, 0) == 1
     * @assert (1, 1) == 2
     */
    public function add($a, $b)
    {
        return $a + $b;
    }
}
?>


生成器会检查原始类中的每个方法查找 @assert 标注。这些标注将会转换为测试代码,诸如

    /**
     * Generated from @assert (0, 0) == 0.
     */
    public function testAdd() {
        $o = new Calculator;
        $this->assertEquals(0, $o->add(0, 0));
    }

下面是运行生成出来的测试用例类得到的结果。

phpunit --bootstrap Calculator.php --verbose CalculatorTest
PHPUnit 3.9.0 by Sebastian Bergmann.

....

Time: 0 seconds, Memory: 3.50Mb

OK (4 tests, 4 assertions)

表 12.1列出了 @assert 标注所允许的的各种变体,以及它们是如何转换为测试代码的。

表 12.1. @assert 标注所允许的各种变体

标注转换为
@assert (...) == XassertEquals(X, method(...))
@assert (...) != XassertNotEquals(X, method(...))
@assert (...) === XassertSame(X, method(...))
@assert (...) !== XassertNotSame(X, method(...))
@assert (...) > XassertGreaterThan(X, method(...))
@assert (...) >= XassertGreaterThanOrEqual(X, method(...))
@assert (...) < XassertLessThan(X, method(...))
@assert (...) <= XassertLessThanOrEqual(X, method(...))
@assert (...) throws X@expectedException X


从测试用例类生成类的骨架

在进行测试驱动开发(参见???)并在编写代码之前先编写其测试时,PHPUnit 能够帮你从测试用例类生成类的骨架。

按照惯例,针对 Unit 类的测试应当写在名为 UnitTest 的类中。PHPUnit 将会测试用例类的源代码,寻找引用了 Unit 类的对象的变量,并分析在这些对象上调用了什么方法。例如,例 12.4 是基于对 例 12.3 的分析而生成的。

例 12.3: BowlingGameTest 类

<?php
class BowlingGameTest extends PHPUnit_Framework_TestCase
{
    protected $game;

    protected function setUp()
    {
        $this->game = new BowlingGame;
    }

    protected function rollMany($n, $pins)
    {
        for ($i = 0; $i < $n; $i++) {
            $this->game->roll($pins);
        }
    }

    public function testScoreForGutterGameIs0()
    {
        $this->rollMany(20, 0);
        $this->assertEquals(0, $this->game->score());
    }
}
?>


phpunit-skelgen --class BowlingGameTest
PHPUnit Skeleton Generator 1.0.0 by Sebastian Bergmann.

Wrote skeleton for "BowlingGame" to "./BowlingGame.php".

例 12.4: 生成的 BowlingGame 类的骨架

<?php
/**
 * Generated by PHPUnit_SkeletonGenerator on 2012-01-09 at 16:55:58.
 */
class BowlingGame
{
    /**
     * @todo Implement roll().
     */
    public function roll()
    {
        // Remove the following line when you implement this method.
        throw new RuntimeException('Not yet implemented.');
    }

    /**
     * @todo Implement score().
     */
    public function score()
    {
        // Remove the following line when you implement this method.
        throw new RuntimeException('Not yet implemented.');
    }
}
?>


下面是针对所生成的类运行测试的结果。

phpunit --bootstrap BowlingGame.php BowlingGameTest
PHPUnit 3.9.0 by Sebastian Bergmann.

E

Time: 0 seconds, Memory: 3.50Mb

There was 1 error:

1) BowlingGameTest::testScoreForGutterGameIs0
RuntimeException: Not yet implemented.

/home/sb/BowlingGame.php:13
/home/sb/BowlingGameTest.php:14
/home/sb/BowlingGameTest.php:20

FAILURES!
Tests: 1, Assertions: 0, Errors: 1.
请在 GitHub 上 开启任务单 来对本页提出改进建议。万分感谢!