下面我将尽可能如实记录实践这个项目的每一个步骤,因为有些步骤在一般情况下是可选的,但在本次实践中可能会因为省略了这些步骤引起前后不一致。
一、修改入口文件:
Zend Framework的单一入口就是index.php文件,默认的文件很简练紧凑,不需要做大幅修改,但我还是对它进行了一点改动。
为了在后续的所有文件中都能有一个相对固定的引用文件的起点,以免在不同的文件中使用相对路径引用文件时迷路,我指定了一个常量,用来指向整个项目的根文件夹,这个常量被命名为ROOT_PATH。后面所有使用相对路径的地方都改成以此为参照的绝对路径引用。
为了使整个程序易于移植,这个常量的定义使用了如下方式:
define ('ROOT_PATH', dirname(dirname(__FILE__)));
|
header('Content-Type:text/html; charset=utf8');
|
使用了 Zend Framework 1.5 的布局模式
require_once 'Zend/Layout.php';
|
Zend_Layout::startMvc(array( 'layoutPath' => '../application/default/layouts', 'layout' => 'main' ));
|
完整的入口文件:
index.php
<?php /** * My new Zend Framework project * * @author lxw(matchless.liu@gmail.com) * @version SVN $Id: index.php 28 2008-07-23 14:27:42Z lxw $ */ define ('ROOT_PATH', dirname(dirname(__FILE__))); set_include_path(ROOT_PATH . '/library' . PATH_SEPARATOR . ROOT_PATH . '/application' . PATH_SEPARATOR . get_include_path()); require_once 'Zend/Controller/Front.php'; require_once 'Zend/Layout.php';
header("Content-Type:text/html;charset=utf8"); /** * Setup controller */ $controller = Zend_Controller_Front::getInstance(); $controller->setControllerDirectory(array(ROOT_PATH . '/application/default/controllers')); $controller->throwExceptions(false); // should be turned on in development time
// bootstrap layouts
Zend_Layout::startMvc(array( 'layoutPath' => '../application/default/layouts', 'layout' => 'main' ));
// run!
$controller->dispatch();
|
二、第一个单元测试
既然是测试驱动开发,当然是要写单元测试了。
现在还什么都没有,只有一个IndexController,其中也没有model,不过要先写一个测试,看看测试文件都需要写些什么才能跑得起来。
这个测试是针对IndexController的,按照之前的路径约定,它应该放在tests/application/default/controllers文件夹中,命名为IndexControllerTest.php,并根据类名约定命名类。
IndexControllerTest.php
<?php require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/TestHelper.php'; require_once 'default/controllers/IndexController.php';
class IndexControllerTest extends PHPUnit_Framework_TestCase { public function testIndexAction() { require_once("Zend/Session.php"); Zend_Session::start(); require_once('Zend/Controller/Front.php'); require_once ('Zend/Controller/Request/Http.php'); require_once ('Zend/Controller/Response/Http.php'); require_once 'Zend/Controller/Router/Rewrite.php'; $front = Zend_Controller_Front::getInstance(); $request = new Zend_Controller_Request_Http(); $response = new Zend_Controller_Response_Http(); $front->setRequest($request);
$controller = new IndexController($request, $response); $controller->indexAction(); } }
|
其中TestHelper.php中是每次测试都需要指定的include_path及对测试框架文件的require。它被放置在tests文件夹中。
TestHelper.php
<?php /** * Ebuyhotel * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * * @category Ebuyhotel * @package UnitTests * @copyright Copyright (c) 2008-2008 lxw (matchless.liu@gmail.com) * @version $Id: TestHelper.php 44 2008-09-24 13:52:15Z lxw $ */
/* * Start output buffering */ ob_start();
/* * Include PHPUnit dependencies */ require_once 'PHPUnit/Framework.php'; require_once 'PHPUnit/Framework/IncompleteTestError.php'; require_once 'PHPUnit/Framework/TestCase.php'; require_once 'PHPUnit/Framework/TestSuite.php'; require_once 'PHPUnit/Runner/Version.php'; require_once 'PHPUnit/TextUI/TestRunner.php'; require_once 'PHPUnit/Util/Filter.php';
/* * Set error reporting to the level to which Ebuyhotel code must comply. */ error_reporting( E_ALL | E_STRICT );
/* * Determine the root, library, and tests directories of the framework * distribution. */ $EbuyhotelRoot = dirname(dirname(__FILE__)); $EbuyhotelTests = "$EbuyhotelRoot/tests"; $EbuyhotelTestsApplication = "$EbuyhotelTests/application"; $EbuyhotelTestsLibrary = "$EbuyhotelTests/library"; $EbuyhotelApplication = "$EbuyhotelRoot/application"; $EbuyhotelLibray = "$EbuyhotelRoot/library";
/* * Omit from code coverage reports the contents of the tests directory */ foreach (array('php', 'phtml', 'csv') as $suffix) { PHPUnit_Util_Filter::addDirectoryToFilter($EbuyhotelTests, ".$suffix"); }
/* * Prepend the Ebuyhotel library/ and application/ and tests/ directories to the * include_path. This allows the tests to run out of the box and helps prevent * loading other copies of the framework code and tests that would supersede * this copy. */ $path = array( $EbuyhotelTests, $EbuyhotelTestsApplication, $EbuyhotelTestsLibrary, $EbuyhotelApplication, $EbuyhotelLibray, get_include_path() ); set_include_path(implode(PATH_SEPARATOR, $path));
/* * Load the user-defined test configuration file, if it exists; otherwise, load * the default configuration. */ if (is_readable($EbuyhotelTests . DIRECTORY_SEPARATOR . 'TestConfiguration.php')) { require_once $EbuyhotelTests . DIRECTORY_SEPARATOR . 'TestConfiguration.php'; } else { require_once $EbuyhotelTests . DIRECTORY_SEPARATOR . 'TestConfiguration.php.dist'; }
/* * Unset global variables that are no longer needed. */ unset($EbuyhotelRoot, $EbuyhotelApplication, $EbuyhotelLibray, $EbuyhotelTests, $EbuyhotelTestsApplication, $EbuyhotelTestsLibrary, $path);
|
运行一下测试文件:右击IndexControllerTest.php,选择快捷菜单中的Run as>3.PHPUnit Test
结果如下图
500)this.width=500;" border="0" width="500">
测试结果很直观,好看,功能也挺强,就是有点慢。
在跑测试的时候会测试代码覆盖率,还会以xml文件的格式记录测试结果,好像不是每次都有必要,为了速度,可以先关掉它:Window>Preferences,选择PHP>PHPUnit,清除Collect Code Coverage statistics和Generate XML report复选框。这可以加快测试的速度。
500)this.width=500;" width="500">
改成能用PHP直接运行的就更好了!说干就干,参考Zend Framework自己的测试,修改结果如下:
IndexControllerTest.php
<?php require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/TestHelper.php'; require_once 'default/controllers/IndexController.php';
if (!defined('PHPUnit_MAIN_METHOD')) { define('PHPUnit_MAIN_METHOD','IndexController_AllTests::main'); }
class IndexController_AllTests { static public function main() { PHPUnit_TextUI_TestRunner::run(self::suite()); } static public function suite() { $testSuite = new PHPUnit_Framework_TestSuite('IndexController AllTests'); $testSuite->addTestSuite('IndexControllerTest'); return $testSuite; } }
class IndexControllerTest extends PHPUnit_Framework_TestCase { public function testIndexAction() { require_once("Zend/Session.php"); Zend_Session::start(); require_once('Zend/Controller/Front.php'); require_once ('Zend/Controller/Request/Http.php'); require_once ('Zend/Controller/Response/Http.php'); require_once 'Zend/Controller/Router/Rewrite.php'; $front = Zend_Controller_Front::getInstance(); $request = new Zend_Controller_Request_Http(); $response = new Zend_Controller_Response_Http(); $front->setRequest($request);
$controller = new IndexController($request, $response); $controller->indexAction(); } }
if (PHPUnit_MAIN_METHOD == 'IndexController_AllTests::main') { IndexController_AllTests::main(); }
|
右击IndexControllerTest.php,选择快捷菜单中的Run as>1. PHP Script,结果如下图:
500)this.width=500;" border="0" width="500">
不过这个测试可就不能以PHPUnit Test的方式运行了。
再改改,参考Zend Framework的测试套件的写法。
IndexControllerTest.php
<?php require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/TestHelper.php'; require_once 'default/controllers/IndexController.php';
if (!defined('PHPUnit_MAIN_METHOD')) { define('PHPUnit_MAIN_METHOD','IndexController_AllTests::main'); }
class IndexController_AllTests extends PHPUnit_Framework_TestSuite { static public function main() { PHPUnit_TextUI_TestRunner::run(self::suite()); } public function __construct() { $this->setName('IndexController_AllTests'); $this->addTestSuite('IndexControllerTest'); } static public function suite() { return new self(); } }
class IndexControllerTest extends PHPUnit_Framework_TestCase { public function testIndexAction() { require_once("Zend/Session.php"); Zend_Session::start(); require_once('Zend/Controller/Front.php'); require_once ('Zend/Controller/Request/Http.php'); require_once ('Zend/Controller/Response/Http.php'); require_once 'Zend/Controller/Router/Rewrite.php'; $front = Zend_Controller_Front::getInstance(); $request = new Zend_Controller_Request_Http(); $response = new Zend_Controller_Response_Http(); $front->setRequest($request);
$controller = new IndexController($request, $response); $controller->indexAction(); } }
if (PHPUnit_MAIN_METHOD == 'IndexController_AllTests::main') { IndexController_AllTests::main(); }
|
以PHPUnit Test方式运行的结果如下图:
500)this.width=500;" border="0" width="500">
咦,怎么有两套测试?
哦,Test和AllTests都被跑了一遍,无伤大雅。
再写个AllTests测试套件,作为对default中的全部controllers的测试,文件放在tests/application/default/controllers文件夹(与IndexControllerTest.php在一起)
AllTests.php
<?php /** * Ebuyhotel * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * * @category Ebuyhotel * @package UnitTests * @copyright Copyright (c) 2008-2008 lxw (matchless.liu@gmail.com) * @version $Id: AllTests.php 26 2008-06-20 16:09:24Z lxw $ */
if (!defined('PHPUnit_MAIN_METHOD')) { define('PHPUnit_MAIN_METHOD', 'Default_Controllers_AllTests::main'); }
/** * Test helper */ require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/TestHelper.php'; require_once dirname(__FILE__).'/IndexControllerTest.php';
class Default_Controllers_AllTests extends PHPUnit_Framework_TestSuite { public static function main() { $result = PHPUnit_TextUI_TestRunner::run(self::suite()); return $result; }
public function __construct() { $this->setName('Default_Controllers_AllTests'); $this->addTestSuite('IndexController_AllTests'); } public static function suite() { return new self(); } }
if (PHPUnit_MAIN_METHOD == 'Default_Controllers_AllTests::main') { Default_Controllers_AllTests::main(); }
|
以PHP Script方式运行,结果如下图:
500)this.width=500;" border="0" width="500">
以PHPUnit Test方式运行,结果如下图:
500)this.width=500;" border="0" width="500">
其它的逐级AllTests可以仿此构建,这个留待以后具体构建时再说。