分享

[PHPUnit]自动生成PHPUnit测试骨架脚本

 bananarlily 2015-08-12

场景

在编写PHPUnit单元测试代码时,其实很多都是对各个类的各个外部调用的函数进行测试验证,检测代码覆盖率,验证预期效果。为避免增加开发量,可以使用PHPUnit提供的phpunit-skelgen来生成测试骨架。只是一开始我不知道有这个脚本,就自己写了一个,大大地提高了开发效率,也不用为另外投入时间去编写测试代码而烦心。并且发现自定义的脚本比phpunit-skelgen更具人性化。所以在这里分享一下。


一个待测试的示例类

假如我们现在有一个简单的业务类,实现了加运算,为了验证其功能,下面将会就两种生成测试代码的方式进行说明。

1
2
3
4
5
6
7
8
9
<?php
class Demo
{
    public function inc($left$right)
    {
        return $left $right;
    }
}

用phpunit-skelgen生成测试骨架

在安装了phpunit-skelgen后,可以使用以下命令来生成测试骨架。

1
phpunit-skelgen --test -- Demo ./Demo.php

生成后,使用:

1
vim ./DemoTest.php

可查看到生成的测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
/**
 * Generated by PHPUnit_SkeletonGenerator 1.2.1 on 2014-06-30 at 15:53:01.
 */
class DemoTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var Demo
     */
    protected $object;
    /**
     * Sets up the fixture, for example, opens a network connection.
     * This method is called before a test is executed.
     */
    protected function setUp()
    {
        $this->object = new Demo;
    }
    /**
     * Tears down the fixture, for example, closes a network connection.
     * This method is called after a test is executed.
     */
    protected function tearDown()
    {
    }
    /**
     * @covers Demo::inc
     * @todo   Implement testInc().
     */
    public function testInc()
    {
        // Remove the following lines when you implement this test.
        $this->markTestIncomplete(
          'This test has not been implemented yet.'
        );
    }
}

试运行测试一下:

1
2
3
4
[test ~/tests]$phpunit ./DemoTest.php    
PHPUnit 3.7.29 by Sebastian Bergmann.
PHP Fatal error:  Class 'Demo' not found in ~/tests/DemoTest.php on line 18

可以看到没有将需要的测试类包括进来。当然还有其他一些需要手工改动的地方。


自定义的测试代码生成脚本

现在改用自定义的脚本 来生成,虽然也有需要手工改动的地方,但已经尽量将需要改动的代码最小化,让测试人员(很可能是开发人员自己)更关注业务的测试。

先看一下Usage.

1
2
[test ~/tests]$php ./build_phpunit_test_tpl.php 
Usage: php ./build_phpunit_test_tpl.php <file_path> <class_name> [bootstrap] [author = dogstar]

然后可以使用:

1
php ./build_phpunit_test_tpl.php ./Demo.php Demo

来预览看一下将要生成的测试代码,如果没有问题可以使用:

1
php ./build_phpunit_test_tpl.php ./Demo.php Demo > ./Demo_Test.php

将生成的测试代码保存起来。注意:这里使用的是“_Test.php”后缀,以便和官方的区分。看下生成的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php
/**
 * PhpUnderControl_AppAds_Test
 *
 * 针对 ./Demo.php Demo 类的PHPUnit单元测试
 *
 * @author: dogstar 20140630
 */
if (!class_exists('Demo')) {
    require dirname(__FILE__) . '/' './Demo.php';
}
class PhpUnderControl_Demo_Test extends PHPUnit_Framework_TestCase
{
    public $demo;
    protected function setUp()
    {
        parent::setUp();
        $this->demo = new Demo();
    }
    protected function tearDown()
    {
    }
    /**
     * @group returnFormat
     */
    public function testIncReturnFormat()
    {
        $left '';
        $right '';
        $rs $this->demo->inc($left$right);
    }
    /**
     * @depends testIncReturnFormat
     * @group businessData
     */
    public function testIncBusinessData()
    {
        $left '';
        $right '';
        $rs $this->demo->inc($left$right);
    }
}

随后,试运行一下:

1
2
3
4
5
6
7
8
[test ~/tests]$phpunit ./Demo_Test.php    
PHPUnit 3.7.29 by Sebastian Bergmann.
..
Time: 1 ms, Memory: 2.50Mb
OK (2 tests, 0 assertions)

测试通过了!!!

起码,我觉得生成的代码在大多数默认情况下是正常通过的话,可以给开发人员带上心理上的喜悦,从而很容易接受并乐意去进行下一步的测试用例完善。

现在,开发人员只须稍微改动测试代码就可以实现对业务的验证。如下示例:

1
2
3
4
5
6
7
8
9
    public function testIncBusinessData()
    {
        $left '1';
        $right '8';
        $rs $this->demo->inc($left$right);
        $this->assertEquals(9, $rs);
    }

然后再运行,依然通过。


脚本源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<?php
/**
 * 单元测试骨架代码自动生成脚本
 * 主要是针对当前项目系列生成相应的单元测试代码,提高开发效率
 *
 * 用法:
 * Usage: php ./build_phpunit_test_tpl.php <file_path> <class_name> [bootstrap] [author = dogstar]
 *
 * 1、针对全部public的函数进行单元测试
 * 2、各个函数对应返回格式测试与业务数据测试
 * 3、源文件加载(在没有自动加载的情况下)
 *
 * 备注:另可使用phpunit-skelgen进行骨架代码生成
 *
 * @author: dogstar 20140630
 * @version: 2.0.0
 */
if ($argc < 3) {
    die("Usage: php $argv[0] <file_path> <class_name> [bootstrap] [author = dogstar]\n");
}
$filePath $argv[1];
$className $argv[2];
$bootstrap = isset($argv[3]) ? $argv[3] : null;
$author = isset($argv[4]) ? $argv[4] : 'dogstar';
if (!empty($bootstrap)) {
    require $bootstrap;
}
require $filePath;
if (!class_exists($className)) {
    die("Error: cannot find class($className). \n");
}
$reflector new ReflectionClass($className);
$methods $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
date_default_timezone_set('Asia/Shanghai');
$objName = lcfirst(str_replace('_'''$className));
$code = "<?php
/**
 * PhpUnderControl_AppAds_Test
 *
 * 针对 $filePath $className 类的PHPUnit单元测试
 *
 * @author: $author " . date('Ymd') . "
 */
";
if (file_exists(dirname(__FILE__) . '/test_env.php')) {
    $code .= "require_once dirname(__FILE__) . '/test_env.php';
";
}
$code .= "
if (!class_exists('$className')) {
    require dirname(__FILE__) . '/' '$filePath';
}
class PhpUnderControl_" . str_replace('_', '', $className) . "_Test extends PHPUnit_Framework_TestCase
{
    public \$$objName;
    protected function setUp()
    {
        parent::setUp();
        \$this->$objName new $className();
    }
    protected function tearDown()
    {
    }
";
foreach ($methods as $method) {
    if($method->class != $classNamecontinue;
    $fun $method->name;
    $Fun = ucfirst($fun);
    if (strlen($Fun) > 2 && substr($Fun, 0, 2) == '__'continue;
    $rMethod new ReflectionMethod($className$method->name);
    $params $rMethod->getParameters();
    $isStatic $rMethod->isStatic();
    $isConstructor $rMethod->isConstructor();
    if($isConstructorcontinue;
    $initParamStr '';
    $callParamStr '';
    foreach ($params as $param) {
        $initParamStr .= "
        \$" . $param->name . " '';";
        $callParamStr .= '$' $param->name . ', ';
    }
    $callParamStr empty($callParamStr) ? $callParamStr substr($callParamStr, 0, -2);
    $code .= "
    /**
     * @group returnFormat
     */ 
    public function test$Fun" . "ReturnFormat()
    {" . (empty($initParamStr) ? '' : "$initParamStr\n") . '
        ' . ($isStatic  "\$rs = $className::$fun($callParamStr);""\$rs = \$this->$objName->$fun($callParamStr);") . "
    }
";
    $code .= "
    /**
     * @depends test$Fun" . "ReturnFormat
     * @group businessData
     */ 
    public function test$Fun" . "BusinessData()
    {" . (empty($initParamStr) ? '' : "$initParamStr\n") . '
        ' . ($isStatic  "\$rs = $className::$fun($callParamStr);""\$rs = \$this->$objName->$fun($callParamStr);") . "
    }
";
}
$code .= "
}";
echo $code;
echo "\n";


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多