在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
转载自: http://blog.csdn.net/oxware/article/details/55875 1. TDD测试驱动开发(Test-Driven Development, TDD),是近年来兴起的一种软件开发方法。作为一项最佳实践,测试驱动开发在XP方法中占有重要地位。其对开发效率、代码健壮性的显著改进已被越来越多的实例项目所证实。 TDD的主要精神可以概括为“测试先行,快速反馈”。在有实际功能代码之前就开始编写测试代码,通过测试来反映设计,使得测试代码成为事实上的设计文档。一开始测试代码甚至不能通过编译,因为其要测试的功能代码还未编写。我们通过编写刚好够用的功能代码使测试尽快通过,并且没有多余的功能。然后再增加测试使其意料之中地失败,于是又产生编码的需要。一定功能完成之后对代码进行重构,然后加入新的功能。这样不断快速的“失败/通过/重构(red/green/refactor)”的循环中,以测试推动编码需求,步步为营,产生简洁易懂、可测试、健壮性强、刚好够用无多余功能的优良代码,形成测试驱动的开发模式。 2. PHP+TDDTDD本质是一种软件工程上的开发方法,应该与具体语言环境没有依赖。但其在不同语言中的具体运用状况是不尽相同的。从语言角度来看,测试驱动开发起源于面向对象的Smalltalk社区,成型于Java语言。以Java的自动化单元测试框架JUnit的推出为标志,TDD跳出了理论和实验,广泛在实践中运用起来。从JUnit衍生出的xUnit 家族成为TDD应用中不可缺少的工具,使TDD在许多语言中的应用成为可能。本文采用的PHPUnit[注1]就是其中之一。 TDD从诞生之始就带着强烈的OO色彩。在Java/C++等完全OO语言中,为了单独隔离测试某一给定的模块(单元测试),使用了多态接口这样的OO技术来实现隔离。 Robert Koss博士与Jeff Langr在CUJ上的《Test Driven Development in C/C++》一文中,讨论了以C这样的过程语言实践TDD的方法,使用宏来实现自动化测试,以连接期(link-time)多态来实现隔离。 对于施行TDD来说,PHP又有其自身的语言特性。首先,PHP支持类,可以说是部分OO的。也因为如此PHPUnit才有可能模仿JUnit的结构。在PHP5中对OO的支持已有了相当大的改进和扩充。但是,PHP并不是生来就设计为OO的,它在很大程度上照顾了C的色彩。而且在国内PHP项目的现状来看,OOD/OOP也并不是主导思想。在广泛运用的稳定版本PHP4.x中,OO方面支持简单继承,但因为弱类型特性,所以谈不上多态的概念。目前国内对TDD方法的关注大多基于Java环境,以PHP为例的讨论并不多见。因此有必要进行一些探讨。 3. 例子我们以利用PHP标准函数explode()实现另一标准函数strtok()[注2]作为例子,具体说明如何在PHP项目中应用TDD。为区别于标准函数,我们将待实现函数命名为strtk()。运行环境为Apache 3.1任务分析 —— 需求、设计、测试 程序员通过向strtk()传递两个字符串参数$str与$sep,以及随后连续调用strtk($sep),会依次得到$str中被$sep里任一字符分割的每个非空子串。事实上,TDD不提倡事先列出详细的设计条款或者形成官僚繁冗的设计文档,因为“测试反映设计”,测试即是编码的设计文档,不断增加的测试对应着不断变化的需求。需求变化中的任何时刻我们的代码都是可用的,因为它们都是保证测试通过的。这与XP方法“主动拥抱变化”的精神是一致的。 3.2第一轮 —— PHPUnit、伪实现模式 首先想到容易测试的是$str中不存在分隔符串$sep的情况,结果应是原串$str。我们开始编写测试用例中的第一个方法:
//test for strtk() require_once "PHPUnit.php";//使用PHPUnit require_once "strtk.php";//被测函数所在文件
//一个测试用例即为一个继承了PHPUnit_TestCase的子类 class test_strtk extends PHPUnit_TestCase { //constructor function test_strtk($TestName) { //调用父类的构造函数以执行名为$TestName的测试方法 $this->PHPUnit_TestCase($TestName); }
//initialization function setUp() { //每个测试方法被执行之前此函数都会被调用 }
//depose function teardown() { //每个测试方法被执行之后此函数都会被调用 }
//第一个测试方法:无匹配字符,应返回原串 function test_no_match() { $teststr="abcd"; $rslt=strtk($teststr,"#"); $this->assertEquals($teststr,$rslt,"无匹配字符时未返回原串"); } }
//执行指定的测试用例类中每一个名字以'test'开头的方法 //此时测试结果输出时将统一小写,故方法命名中用'_' $tsStrtk=new PHPUnit_TestSuite(test_strtk); $TestRslt=PHPUnit::run($tsStrtk);//执行获得结果 echo $TestRslt->toHTML();//结果在浏览器中输出 ?>
保存为test_strtk.php,执行得到结果:
Fatal error: Failed opening required 'strtk.php'
意料之中,因为要被测试的文件和函数都还不存在。现在测试要求我们创建文件strtk.php并编写函数strtk():
//strtk.php function strtk($str,$sep) { return $str;//Fake-It Pattern } ?>
执行test_strtk.php,测试通过:
TestCase test_strtk->test_no_match() passed
在TDD中,最重要的是尽快让测试通过。直接返回$str看起来似乎不可理喻,但确实是让测试最快通过的方法。这正符合TDD中的“伪实现模式(Fake-It Pattern)”。伪实现马上就会因为测试的增加而被替换掉。但是为什么不直接硬编码返回”abcd”呢?因为返回$str这个显而易见的抽象在实际中并不耗费我们比硬编码更多的时间。当抽象不那么轻松的时候,我们还有下面的方法来用测试驱动抽象的必要性,保证抽象的正确性。
3.3第二轮 —— 三角模式
调用一次strtk()应该能获得被分割的第一个子串。我们在test_strtk类中增加测试方法:
//获得第一个子串 function test_get_first_substr() { $teststr="one,two,three"; $rslt=strtk($teststr,","); $this->assertEquals("one",$rslt,"未能获得第一个子串"); //Triangulate Pattern $teststr="Feb./8/2004"; $rslt=strtk($teststr,"/"); $this->assertEquals("Feb.",$rslt,"未能获得第一个子串"); }
运行测试,和预计的一样得到失败的结果:
TestCase test_strtk->test_no_match() passed TestCase test_strtk->test_get_first_substr() failed: 未能获得第一个子串 expected one, actual one,two,three TestCase test_strtk->test_get_first_substr() failed: 未能获得第一个子串 expected Feb., actual Feb./8/2004
直接返回原串行不通了。通过测试实践还可以证明尝试硬编码返回”one”或者”Feb.”也都不行。这个测试方法中同时存在两个断言,利用了“三角模式(Triangulate Pattern)”提出了进一步抽象的需要。测试要求我们利用explode()函数了:
2022-08-30 2022-07-18 2022-08-17 2022-11-06 2022-07-29 |
请发表评论