php教程:php设计模式介绍之伪对象模式(5)
现在让我们来创建一些测试的实例来详细说明重构后的运用程序应该有的功能。
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)的方法来创建你所希望的从对象(如同你在测试代码时所做的那样)返回的结果.下一步,实例化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>’);
}
}
如同这段程序代码一样,测试代码本身也可以进行重构。在本例中,你可以看到缓存输出的诀窍是其将被多次复用,因此使用“析构法”重构可以使测试本身简化。(重新调用的那些以“test”为开头的方法是随整个测试一起自动运行的;你也可以自己创建一些使测试更简洁的方法。)