场景
在编写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后,可以使用以下命令来生成测试骨架。
1phpunit-skelgen --test -- Demo ./Demo.php
生成后,使用:
1vim ./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 DemoTestextends 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 foundin ~/tests/DemoTest.php on line 18
可以看到没有将需要的测试类包括进来。当然还有其他一些需要手工改动的地方。
自定义的测试代码生成脚本
现在改用自定义的脚本 来生成,虽然也有需要手工改动的地方,但已经尽量将需要改动的代码最小化,让测试人员(很可能是开发人员自己)更关注业务的测试。
先看一下Usage.
1
2[test ~/tests]$php ./build_phpunit_test_tpl.php
Usage: php ./build_phpunit_test_tpl.php [bootstrap] [author = dogstar]
然后可以使用:
1php ./build_phpunit_test_tpl.php ./Demo.php Demo
来预览看一下将要生成的测试代码,如果没有问题可以使用:
1php ./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_Testextends 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
9public 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 [bootstrap] [author = dogstar]
*
* 1、针对全部public的函数进行单元测试
* 2、各个函数对应返回格式测试与业务数据测试
* 3、源文件加载(在没有自动加载的情况下)
*
* 备注:另可使用phpunit-skelgen进行骨架代码生成
*
* @author: dogstar 20140630
* @version: 2.0.0
*/
if ($argc
die("Usage: php $argv[0] [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) . "_Testextends PHPUnit_Framework_TestCase
{
public \$$objName;
protected function setUp()
{
parent::setUp();
\$this->$objName =new $className();
}
protected function tearDown()
{
}
";
foreach ($methods as $method) {
if($method->class !=$className)continue;
$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($isConstructor)continue;
$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";