《PHP设计模式介绍》第六章 伪对象模式(3)_PHP教程
推荐:《PHP设计模式介绍》第五章 注册模式我们通常认为避免使用全局变量是一种好的选择,因此,对象经常被作为参数从一段代码传递到另一段。但是传递实例的一个问题就是对象有时候不知道将要传递给谁——?经过一个函数后才被传
这个“bootstrap”文件运行新程序时应如下所示:
<?php require_once ‘classes.inc.php’; define(‘SELF’, ‘http://www.example.com/path/to/page.php’); $page =& new PageDirector(new Session, new Response); $page->run(); ?> |
该文件包含了所需的已定义类,并为自己定义了一个常量,给PageDirector类(其用于传递类Session 和类Response所依赖的实例来组成构造函数)创建了一个实例来执行PageDirector::run()方法。
现在让我们来创建一些测试的实例来详细说明重构后的运用程序应该有的功能。
require_once ‘simpletest/unit_tester.php’; require_once ‘simpletest/reporter.php’; require_once ‘simpletest/mock_objects.php’; require_once ‘simpletest/web_tester.php’; require_once ‘classes.inc.php’; Session::init(); class PageWebTestCase extends WebTestCase { /*...*/ } class ResponseTestCase extends UnitTestCase { /*...*/ } class UserLoginTestCase extends UnitTestCase { /*...*/ } class SessionTestCase extends UnitTestCase { /*...*/ } class PageDirectorTestCase extends UnitTestCase { /*...*/ } $test = new GroupTest(‘Application PHP4 Unit Test’); $test->addTestCase(new PageWebTestCase); $test->addTestCase(new ResponseTestCase); $test->addTestCase(new UserLoginTestCase); $test->addTestCase(new SessionTestCase); $test->addTestCase(new PageDirectorTestCase); |
这段代码或多或少的展示了一个典型的运用程序的测试文件该是何种模样。它一开始就包含了一些SimpleTest文件,也包括了用伪对象来测试的mock_object.php文件。接着,那些辅助类被包含进来,方法Session::init()被调用,seesion开始。
紧接着的全是以“安全无害”为目标而开始的测试实例,类WebTestCase确保所有程序按要求执行, 然后是单独的用于新设计的类的测试(尽管这种类本章不会详述)。最后是我们接下去会讨论的PageDirectorTestCase类。
类PageDirector的核心任务是协调类Session和类Response的对象,产生最终的网页输出结果。
Mock::Generate(‘Session’); Mock::Generate(‘Response’); define(‘SELF’, ‘testvalue’); class PageDirectorTestCase extends UnitTestCase { // ... } |
在这段代码的一开始,Mock::generate()创建了伪对象类的定义并定义了一个后面将要用到的常量。
假设对类Session 和类 Response的测试已经存在,下一步就是创建伪Session来模拟类 Session的状态。这个伪对象的设置和我们一开始所演示的例子极其类似。
因为PageDirector::run()方法正回显内容,你可以用输出缓存内容的办法来捕获它,看看是否正确。
class PageDirectorTestCase extends UnitTestCase { // ... function TestLoggedOutContent() { $session =& new MockSession($this); $session->setReturnValue(‘get’, null, array(‘user_name’)); $session->expectOnce(‘get’, array(‘user_name’)); $page =& new PageDirector($session, new Response); ob_start(); $page->run(); $result = ob_get_clean(); $this->assertNoUnwantedPattern(‘/secret.*content/i’, $result); $this->assertWantedPattern(‘/<form.*<input[^>]*text[^>]*’ .’name.*<input[^>]*password[^>]*passwd/ims’ ,$result); $session->tally(); } } |
这段代码证明了在SimpleTest中使用伪对象的必要性。我们来看看其中创建伪对象的一行代码$session =&new MockSession($this)。你可以使用继承自SimpleStub类(参见http://simpletest.sf.net/SimpleTest/MockObjects/SimpleStub.html#sec-methodsummary)的方法来创建你所希望的从对象(如同你在测试代码时所做的那样)返回的结果.下一步,实例化PageDirector类并用MockSession代替正式使用时的类来实例化相关代码。
注:setReturnValue()方法
setReturnValue()方法通过指定当伪对象的特定方法被调用时返回何值来让伪对象以一个“替身”的身份融入代码。已经有了一些这种方法的变体:比如指定以一定次序返回一系列值的做法,还有以参数代替值来返回结果的做法。
expectOnce()方法
expectOnce()方法通过建立一些假想,这些假想是关于什么时候方法被调用以及多久调用一次,来允许你的伪对象以“批评者”的角色来测试代码。这些假想当你在测试中调用伪对象的tally()方法时会被报告。
class PageDirector { var $session; var $response; function PageDirector(&$session, &$response) { $this->session =& $session; $this->response =& $response; } } |
因为PageDirector类认为自己不是处于一个测试环境而是处于一个真实正常的运用程序环境中,它回显结果到浏览器。既然你实际上在测试时并不希望这个动作,你就可以通过PHP输出缓存的特性(参见http://php.net/outcontrol)来捕获执行时它往浏览器发送了什么。
class PageDirector { // ... function run() { if (!$this->isLoggedIn()) { $this->showLogin(); } this->response->display(); } function isLoggedIn() { return ($this->session->get(‘user_name’)) ? true : false; } function showLogin() { $this->response->addBody(‘<form method=”post”>’); $this->response->addBody(‘Name:<input type=”text” name=”name”>’); $this->response->addBody(“\n”); $this->response->addBody( ‘Password:<input type=”password” name=”passwd”>’); $this->response->addBody(“\n”); $this->response->addBody(‘<input type=”submit” value=”Login”>’); $this->response->addBody(‘</form>’); } } |
下面的代码段演示了缓存输出被重构为runPage方法的结果,它给人的感觉就像是当用户登录时另一个对输出的测试。
class PageDirectorTestCase extends UnitTestCase { // ... function TestLoggedOutContent() { $session =& new MockSession($this); $session->setReturnValue(‘get’, null, array(‘user_name’)); $session->expectOnce(‘get’, array(‘user_name’)); $page =& new PageDirector($session, new Response); $result = $this->runPage($page); $this->assertNoUnwantedPattern(‘/secret.*content/i’, $result); $this->assertWantedPattern(‘/<form.*<input[^>]*text[^>]*’ .’name.*<input[^>]*password[^>]*passwd/ims’ ,$result); $session->tally(); } function TestLoggedInContent() { $session =& new MockSession($this); $session->setReturnValue(‘get’, ‘admin’, array(‘user_name’)); $session->expectAtLeastOnce(‘get’); $page =& new PageDirector($session, new Response); $result = $this->runPage($page); $this->assertWantedPattern(‘/secret.*content/i’, $result); $this->assertNoUnwantedPattern(‘/<form.*<input[^>]*text[^>]*’ .’name.*<input[^>]*password[^>]*passwd/ims’ ,$result); $session->tally(); } function runPage(&$page) { ob_start(); $page->run(); return ob_get_clean(); } } |
接下来,将加入一个检查条件到PageDirector::run()方法来看看用户是否已经登录并决定显示什么模板:
class PageDirector { // ... function run() { if ($this->isLoggedIn()) { $this->showPage( new UserLogin($this->session->get(‘user_name’))); } else { $this->showLogin(); } $this->response->display(); } function showPage(&$user) { $vars = array( ‘name’ => $user->name() ,’self’ => SELF ); $this->response->addBodyTemplate(‘page.tpl’, $vars); } } |
page.tpl看上去可能像这样:
Welcome <?php echo $name; ?> <br>Super secret member only content here. <a href=”<?php echo $self; ?>?clear”>Logout</a> |
此时,MockSession扮演了ServerStub的角色来控制决定用户是否登录的条件。它的功能也类似评判者,决定这个信息是否通过如下两个途径被正确的使用:一个是明确地被预先定义并通过tally()被验证,另一个是不直接的生成正确的输出,而是通过ServerStub返回的值来生成。
为了继续重构这段代码,下一步要跳到前面的进程。将要做两个动作:清除已经登录的用户和验证登录页面提交的用户名和密码是否存在。
分享:《PHP设计模式介绍》第四章 单件模式几乎所有面向对象的程序中,总有一两个资源被创建出来,在程序应用中持续被共享使用。例如,这样的一个资源,在一个电子商务程序的数据库连接中使用:这个连接在应用程序启动时初始化,程序于是
- 相关链接:
- 教程说明:
PHP教程-《PHP设计模式介绍》第六章 伪对象模式(3)。