关于php测试部署和持续集成 | ||||||||||||||||
在开发具有一定复杂程度的应用程序的过程中,会遇到bug,逻辑错误和合作等难题,这些问题处理得很好就会成功地开发应用程序,否则就会得到延期、超出预算的应用程序,以及雇员流失问题。 这些问题是不可预防的,但是有一系列的工具可以帮助你更好地管理项目,并实时跟踪项目的进展。这些工具组合在一起会形成一种被称为持续集成(continuous integration)的编程技术。 任何持续集成项目都包含四个主要的组件:版本控制、单元测试、部署和调试。通常,在使用PHP语言的项目中,这四个领域所用的工具分别为Subversion、PHPUnit、Phing 和Xdebug。为了将这些工具全部连接在一起,可以使用持续集成服务器Xinc。 1 用作版本控制的Subversion Subversion是一个版本控制系统(通常缩写为SVN),可以用来跟踪对应用程序文件作出的修改。如果是PHP开发人员,应该很熟悉版本控制了。你用过的可能是并行版本系统(CVS),这是Subversion之前的系统,并且仍然被广泛使用。 在两个或者多个开发人员修改同一个文件的情况下,Subversion有助于预防一种常见的情形的发生。如果不用版本控制系统,开发人员需要下载源文件(通常是从FTP服务器上),做出修改,然后再上传文件,覆盖原来的文件。如果另外一个开发人员同时下载了相同的源文件,也做了一些修改,然后上传文件,就会覆盖之前的那个开发人员的工作。 使用Subverion,这一情形不会再发生。不用下载文件,开发人员需要签出(check out)文件的当前版本,做出修改,然后提交(commit)这些修改。 在提交过程中,Subverion会检查是否有其它用户在这个文件被下载之后对文件作出修改。如果文件已经被修改,Subversion会试图合并所有的修改,以便使最终的文件包含所有的修改内容。如果这些修改不会影响到文件的同一部分,这样处理没有任何问题。不过,如果修改了相同代码,冲突就会发生。最后的提交者要负责将他的修改内容与之前的修改内容集成起来。使用这一方法不会丢失任何工作,并且项目会保持内部一致。 8.1.1 安装Subversion 在几乎所有的Linux发行版中,Subversion都可以通过包管理程序安装。如果使用Debian/Ubuntu风格的包管理程序,需要输入以下命令安装Subversion。 > apt-get install subversion subversion-tools 这会为你提供创建本地Subversion存储库所需要的所有工具。存储库(repository)是一个受到版本控制的文件和文件夹的目录。通常,可以为多个项目创建多个存储库,这些工具允许你在服务器上管理它们。 说明 Subversion 在设计上可以通过Apache web服务器来远程操作。要实现这一功能,需要另外安装libapache2-svn 包,它提供了Apache和Subversion之间的绑定。然后,应该特别注意正确地设置服务器的安全性。如果选择使用Apache和Subverion,推荐你部署安全套接字层(SSL)的客户端证书,就像第 21章所介绍的那样。 1.2 设置Subversion 管理Subversion存储库实际上非常简单。首先,需要在服务器上找到一个合适的位置保存存储库。推荐放在/usr/local/svn中,不过其它地方是没有问题的。下一步,使用svnadmin create 命令在这个目录中创建一个存储库。 > svnadmin create myfirstrepo 现在会看到一个新的目录( /usr/local/svn/myfirstrepo ),它包含了管理项目所需要的所有文件和数据库。 下一步是获得存储库的一个工作签出副本。签出副本( checkout ) 是Subversion的工作空间,可以在这里添加文件以及修改文件。不要在存储库目 录中直接修改文件,这一点非常重要。为了创建签出副本,需要转到一个新的 目录,并执行svn checkout命令,建议使用home目录。如下所示。 > cd ~ > svn checkout file:///usr/local/svn/myfirstrepo checked out revision 0. 注意 不要在包含存储库的目录 /usr/local/svn 中执行svn checkout 命令。 现在便可以看到存储库目录了。如果有一个现存的项目,可以使用 svn import 命令将这些文件置于版本控制之下了,如下所示。 > svn import ~/existingproject file:///usr/local/svn/myfirstrepo 你将需要输入一个提交信息。这些信息是很关键的,它可以保存修改的人、修改的文件以及修改的原因。对于初始导入过程来说,只需说明Initial Import of <Project> 即可,然后保存文件。 提示 通过设置EDITOR 环境变量,可以修改Subversion用来输入提交信息的编辑器,例如,在Bash外壳环境中,可以使用export EDITOR=pico命令将编辑器修改为Pico。 这个项目现在已经置于版本控制之下了,但是导入之前创建的签出副本现在已经过期了,它并没有反映导入的内容。这是故意设计成这样的,所有的签出副本必须使用svn update命令来手工更新。 > svn update A index.html updted to version 1. 提示 在修改文件之前,经常更新Subversion 会减少需要处理的合并的次数。 现在,当你修改文件时,便是在签出副本中修改它们。实际上,你可以备份原来的文件,因为不再需要对这些文件进行处理了。 你应该会注意到,在签出副本的每个目录下都有一个.svn目录。在某些情况下,你可能希望获取一个没有这些目录的副本,例如在创建应用程序的发行版本时。要获得一个不包含工作目录的项目的副本,可以使用svn export命令。 > svn export file:///usr/local/svn/myfirstrepo ~/exportdirectory A /home/user/exportdirectory A /home/user/exportdirectory/index.html Exported revision 1. 要向存储库添加新文件,你需要使用svn add命令,添加文件是本地的修改行为,这和导入文件不同,不会被保存到存储库中,除非使用svn commint命令(这个命令下面将会讨论到)显示地保存这一修改。 > echo test > newfile.txt > svn add newfile.txt A newfile.txt > svn commit Adding newfile.txt Transmitting file data . Committed revision 2. 1.3 提交修改和解决冲突 目前,文件已经处于版本控制之下了,你可以根据需要去修改它们,当要将修改保存到存储库时,需要提交它们。为了确定是否有需要提交的修改, 可以使用svn status 命令。 > echo changed > newfile.txt > svn status M newfile.txt 这一例子显示出newfile.txt 文件已经被修改过了。在文件名旁边的M字符表示所有本地的文件修改已经与存储库中的修改合并在一起了。因此,应该提交这些修改内容。 如果不想保留修改内容,可以使用svn revert 命令来恢复旧文件。 > svn revert newfile.txt Reverted 'newfile.txt' > cat newfile.txt test 下一步是模拟同一项目中另一个开发人员的情形,需在home目录中创建第二个签出副本。 > svn co file:///usr/local/svn/myfirstrepo ~/myfirstrepo2 A /home/user/myfirstrepo2/newfile.txt A /home/user/myfirstrepo2/indeex.html Checked out revisiion 2. 然后,在myfirstrepo2目录下向newfile.txt文件添加一些内容。 > echo newdata >> newfile.txt > svn commit Sending newfile.txt Transmitting file data . Committed revision 3. 返回到myfirstrepo目录,并且不要去更新它。打开newfile.txt文件,注意到另外一个签出副本的修改还没有反映到这个文件中。现在,在这个签出副本中对文件中的同一行做一个相似但不同的修改,并且尝试去提交它。 会出现一个表示过期的错误提示,表明其他人已经修改了这个文件。你必须获得文件的最后版本才能提交修改。现在执行更新操作会导致文件处于冲突状态,这是因为在两个签出副本中对同一行内容都做了修改。 > echo alternativedata >> newfile.txt > svn commit Sending newfile.txt svn: Commit failed (details follow): svn: Out of date: 'newfile.txt' in transaction '3-1' > svn udpate C newfile.txt Updated to revision 3. 请注意newfile.txt 旁边的C字符。这一字符表明文件处于冲突状态。 如果在这个目录中运行ls命令,将会看有三个新文件已经被创建。 > ls -l index.html newfile.txt newfile.txt.mine newfile.txt.r2 newfile.txt.r3 这些文件表示了冲突状态。r2文件是原始的文件,r3文件包含了在 myfirstrepo2中所做的修改,.mine文件是本地的修改。.txt文件也已经被修 改,并且现在包含的修改内容,从而可以更容易地解决冲突。 > cat newfile.txt test <<<<<<<<< .mine alternativedata ========== newdata >>>>>>>>> .r3 现在需要newfile.txt文件包含所有的修改内容。首先将<<<、>>>文本行和===文本删除,然后添加或者删除对文件的修改内容,以便得到理想的最终结果。在更加复杂的合并情况中,如果遇到功能互相覆盖的情况,可以拒绝特定的修改或者重新处理这些内容。不过,在这个例子中,希望同时保留newdata修改和本地修改。最终的文件应该是这样写的。 > cat newfile.txt test newdata alternativedata 下一步需要告诉Subversion已经解决了冲突,使用svn resolved 命令可以达到这一目的。 > svn resolved newfile.txt Resolved conflicted state of 'newfile.txt' 这一操作删除了三个额外的文件。通过帮助解决冲突,这些文件已经完成了它们的全部功能,现在已经不再需要它们了。 最后一步是调用svn commit命令提交已经解决好的修改内容。 svn commit Sending newfile.txt Transmitting file data . Committed revision 4. 可以看到,使用这一过程,开发人员就不会轻易地覆盖他们的代码了。 1.4 激活对Subversion 的访问功能 下一步是通过Apache激活对Subversion 的访问功能。要实现这一目的,需要在Apache Web 服务器上创建一个虚拟主机。 创建好虚拟主机之后,只需要遵循以下格式向配置文件添加一个 <Location>标签就可以了。 <Location /svn/myfirstrepo> DAV svn SVNPath /usr/local/svn/myfirstrepo </Location> 现在,可以从其他客户端位置使用查找出自己的文件,而不是file:///usr/local/svn/myfirstepo ,因为这一路径只适合在本地服务上的访问。 注意 这里显示<Location>标签完全没有考虑安全性因素。在显示任何实际的代码之前,请确保已正确地配置好验证和SSL安全功能,关于SSL客户端证书的安装指南 2 用于单元测试的PHPUit PHPUnit 可以用来为应用程序创建单元测试。简单来说,PHP的单元测试包括编写专门用来测试其他PHP脚本的PHP脚本。这种类型的测试被称为单元测试,这是因为测试装置是用来测试单独的代码单元的,如类和方法,而且一次只测试一个单元。PHPUnit是用来编写这些测试的一个优秀的解决方案,并且遵循了面向对象的开发方法。 2.1 安装PHPUnit 安装PHPUnit是通过PEAR完成的,而且过程非常简单。首先使用PEAR来“发现”pear.phpunit.de频道。 > pear channel-discover pear.phpunit.de Adding Channel "pear.phpunit.de" succeeded Discovery of channel "pear.phpunit.de" succeeded 然后,安装PHPUnit以及它所需要的附属项。 > pear install --alldeps phpunit/PHPUnit downloading PHPUnit-3.1.9.tgz ... starting to download PHPUnit-3.1.9.tgz (116,945 bytes) ...................................done : 116,945 bytes install ok : channel://pear.phpunit.de/PHPUnit-3.1.9 根据系统上的PEAR布局,PHPUnit源文件应该可以在/usr/share/php/PHPUnit目录中找到。 2.2 创建第一个单元测试 要开始创建单元测试,需要设置一个目录结构。将测试放在哪里没有一个特定的规范。一些开发人员将测试文件与被测试的代码放在相同的目录中。其他人创建一个单独的测试目录,并且复制了代码目录的结构,这样有助于保持测试代码的独立性。在这个示例中使用了后一种方法。 首先,清除Subversion文件。 > svn rm index.html newfile.txt D index.html D newfile.txt > svn commit Deteing index.html Deteing newfile.txt Committed revision 5. 说明 在svn commit 命令下使用svn rm 命令会同时删存储库和签出 副本中的文件。标准的rm 命令不会从存 储库中删除文件,文件将会在下一次使用svn update时恢复。 现在创建两个目录,一个用于保存代码,另外一个用于保存测试。 > svn mkdir code tests A code A tests 在code 目录中创建一个Demo类,这个类实现了一些很容易测试的操 作,即加法和减法,如代码清单8-1所未。 代码清单8-1 Demo类(./code/Demo.php) <?php class Demo { public function sum($a , $b){ return $a+$b; } public function subtract($a, $b){ return $a-$b; } } 下一步是创建如代码清单8-2所示的单元测试。 代码清单8-2 单元测试代码(./test/Demotest.php) <?php require_once('PHPUnit/Frameword.php'); require_once('dirname(__FILE__).'/../code/Demo.php '); class DemoTest extends PHPUnit_Framwork_TestCase { public function testSum(){ $demo = new Demo(); $this->assertEquals(4,$demo->sum(2,2)); $this->assertNotEquals(3,$demo->sum(1,1)); } } ?> 现在,在tests目录中,使用phpunit测试运行程序来运行测试。 > phpunit DemoTest PHPUnit 3.1.9 by Sebastian Bergmann. Time: 0 seconds OK (1 test) 可以看到,测试运行程序给出了测试已正确运行的信息。 2.3 理解PHPUnit 现在你已经了解了在哪里放置单元测试代码以及如何从命令行调用它们,你可能会问PHPUnit_Framework_TestCase到底是什么,testSum()方法是如何工作的呢, PHPUnit测试通常遵循一个命名规范,即测试类的名称就是被测试类的名称加上单词Test,并且这个类的文件名称正是测试类的名称加上.php扩展名。测试类名称应该是组件的描述或者被测试的功能的描述。例如,测试用户验证功能(Authentication 类)的类应该命名为AuthenticationTest,并且被保存在AuthenticationTest.php中。 测试,更明确地说是测试用例,只是一个从PHPUnit_Framwork_TestCase继承的类。这个测试类提供了访问所有不同类型断言的功能,并且负责运行针对目标类的所有测试方法。 在这个例子中,当给测试运行器传入DemoTest类时,它会一次调用一个测试方法,并且同时收集信息,在每一个方法中,应该定义一套关于被测试代码执行功能的假设情况。这些假设需要被翻译成断言。如果希望sum()方法在处理2和2这两个参数时总是返回4,那么应该编写一个函数结果等于4的断言。 不修改DemoTest类而是修改Demo类似便使得它的sum()方法不再返回有效结果,比如将+操作符改为-操作符就能达到这一目的。修改Demo类之后,当再次运行单元测试时,就会得到如下结果。 > phpunit DemoTest PHPUnit 3.1.9 by Sebastian Bergmann. Time: 0 seconds There was 1 failure: 1) testSum(DemoTest) Failed asserting that <integer:0> matches expected value <integer:4>. /home/user/myfirstrepo/tests/DemoTest.php:12 FAILURES1 Tests:1, Failures:1. 测试失败的原因是sum()方法不再通过2+2=4的相等断言。 你可以选择使用几十种断言。在这个例子中,使用的是assertEquals断言和assertNot-Equals断言,这两个断言都是比较传入的两项是否相等的断言。另外,还有用于对象的assertSame断言、用于Boolean类型的assertFalse断言或assertTure断言,以及用于异常的setExpectedException断言。要想获得这些断言的参考列表和准确的语法细节,可参见网站上的PHPUnit手册。 在编写单元测试时,需要设置对象,就像在之前的测试中采用$demo = new Demao语句给对象赋值一样。如果你总是为每个测试都做相同的设置,这种做法就显得烦琐。幸好,PHPUnit提供了两个分别叫做setUp() 和tearDown()的方法,这两个方法允许为每个测试定义公共的配置。代码清单8-3显示了相同的测试代码,分开放置在setUp()方法和tearDown()方法中。
|