先来一个SimpleTest的简单的演示。
我们假设我们正在测试一个简单的文件登陆类。这个叫做Log的类放在classes/log.php里。 我们用以下代码来创建一个测试脚本放在tests/log_test.php里.
autorun.php不仅包含了Simpletest的相关文件,还能够把测试自动执行起来。
TestOfLogging是我们的第一测试用例,此时还是空的。每一个测试用例都是一个继承SimpleTest的类,我们可以在一个文件里写多个测试用例。
代码的第三行,引入了Log类,现在我们有了测试框架,但还没有写具体的测试。
第一个测试,我们假定Log类通过构造函数传入要存储log信息的文件名,并且我们有临时文件夹来放置这个文件
<?php require_once(‘simpletest/autorun.php’); require_once('../classes/log.php');
class TestOfLogging extends UnitTestCase { function testLogCreatesNewFileOnFirstMessage() { @unlink('/temp/test.log'); $log = new Log('/temp/test.log'); $this->assertFalse(file_exists('/temp/test.log')); $log->message(‘Should write this to a file’); $this->assertTrue(file_exists('/temp/test.log')); } } ?>
当一个测试用例执行时,它会搜索所有以"test"开头的方法来并且执行。 这里我们用了一个很长的方法名testLogCreatesNewFileOnFirstMessage(),这是一个好习惯,可以让测试的输出信息更有可读性。
通常一个测试用例里会有多个测试方法,后边会具体来讲。
测试方法中的断言(assertion)会触发测试框架来即时显示测试结果。这个即时反馈很重要,不仅发生在代码导致的崩溃时,也用来显示与断言有关的调试信息。
要看测试结果,我们需要实际地执行一下测试。不需要再写代码了,只要在浏览器里打开这个页面就可以了 测试失败会显示以下信息…
1/1 test cases complete. 1 passes and 1 fails.
通过测试则会显示以下信息…
如果你得到这样的信息…
表示你没准备好classes/Log.php这个文件…
<?php class Log { function Log($file_path) { }
function message() {
}
} ?>
在测试之后写代码,这样的开发方式称之为“测试驱动开发”
创建测试套装
与只运行一个测试用例的应用程序不同,测试套装需要在一个测试脚本中测试多个用例,如果有需要,则执行针对应用程序的所有测试.
我们的第一步是创建一个叫做tests/all_tests.php的新文件.然后插入以下代码…
<?php require_once(‘simpletest/autorun.php’);
class AllTests extends TestSuite { function AllTests() { $this->TestSuite(‘All tests’); $this->addFile(‘log_test.php’); } } ?>
引入"autorun"允许我们调用这个脚本时,直接执行测试套装(TestSuite)。
TestSuite的子类必须显式调用它的构造函数,这个限制会在未来的版本中删除。
TestSuite::addFile()方法会include测试用例文件,并且读取所有继承自SimpleTestCase的新类.UnitTestCase是继承SimpleTestCase类的一个例子,你也可以自己创建。TestSuite::addFile()也可以include 其他的testSuites
此时,这些类还未被实例化,当test suite执行时,他会实例化每个类并执行测试,然后直接销毁。这意味每个测试用例执行前都会被构造,而在下一个用例开始前被析构。
一般情况下,测试用例会继承一些超类,这些类是不应该被运行的,而是作为其他测试的基类。“autorun”要正确的运行,就不能盲目地执行这些类,否则会导致测试结果统计不正确。解决起来很简单,只要把这些类声明为抽象类就可以避免以上问题。因为SimpleTest不会去执行一个抽象类。如果你仍在使用PHP4,用SimpleTestOptions::ignore()也能起到同样的作用。
测试用例文件不应该在别处被包含,否则会导致用例添加不进来。TestSuite::addFile()方法不会去检测一个已经被PHP加载的测试用例类。
要看测试结果,我们只需要通过 web server或命令行调用tests/all_tests.php
使用伪装对象(mock object)
让我们向前看,来做点真正有挑战的事情。
假定我们的logging class通过了测试。同时假定我们要测试另外一个叫做SessionPool的类,它依赖Log来写日志信息。我们想要测试的方法可能会长成这个样子…
class SessionPool { //… function logIn($username) { //… $this->_log->message(“User $username logged in."); //… } //… }
基于复用的理念,我们要使用我们的Log类,一个常规的测试用例可能会长成这个样子…
<?php require_once(‘simpletest/autorun.php’); require_once('../classes/log.php'); require_once('../classes/session_pool.php');
class TestOfSessionLogging extends UnitTestCase {
function setUp() {
@unlink('/temp/test.log');
}
function tearDown() {
@unlink('/temp/test.log');
}
function testLoggingInIsLogged() {
$log = new Log('/temp/test.log');
$session_pool = &new SessionPool($log);
$session_pool->logIn('fred');
$messages = file('/temp/test.log');
$this->assertEqual($messages[0], "User fred logged in.n");
}
} ?>
我们会在后边解释setUp()和tearDown()
这个测试用例的设计并不太坏,但还有改进的余地.我们在日志文件上花费了一些时间,但这并不是我们测试的一部分.我们在Log和测试之间建立了不必要的耦合.如果我们不用文件而换用syslog库,结果会怎样呢?这意味着TestOfSessionLogging测试会失败,尽管这并不是日志类的测试.
在更细微的方面,它同样脆弱,你注意到message信息吗?你注意到message中添加的额外信息了吗?如果多一个时间戳或者其他信息会怎样。
我们唯一真正想要测试的是一个指定的信息被发送到日志对象。如果我们传入一个只简单记录信息的伪日志对象,则可以减少耦合。当然,这个伪对象必须和原对象长得很像。
如果这个伪对象不写文件,我们就可以省下清理文件的代码,如果这个对象着可以执行断言操作,则可以节省更多的代码。
不要认为这只是空想?我们可以简单的创建这个对象…
<?php require_once(‘simpletest/autorun.php’); require_once('../classes/log.php'); require_once('../classes/session_pool.php');
Mock::generate(‘Log’);
class TestOfSessionLogging extends UnitTestCase {
function testLoggingInIsLogged() {
$log = &new MockLog();
$log->expectOnce('message', array('User fred logged in.'));
$session_pool = &new SessionPool($log);
$session_pool->logIn('fred');
}
} ?>
Mock::generate() 创建一个新class,叫做MockLog。这就像一个克隆操作,除非我们可以植入测试代码.expectOnce()做的就是这个操作.它表示如果message()被call,那么传入的参数应该是"User fred logged in.”.
当SessionPool::logIn()调用MockLog对象的message()被调用时,测试就会被触发.这个伪装对象会对比传入的参数,并显示测试结果. 这里可以使用通配符.
如果mock的指定方法到测试结束也没有被调用,那么expectOnce()会触发一个失败信息.换句话说,mock既可以检测动作被执行,也可以检测到动作的缺失.
mock对象在SimpleTest中可以设置返回值,返回的序列,设置根据输入的参数返回的值,设置期望的参数序列,设置方法被调用的次数的限制.
网页测试
如果你想要对一个web项目进行完整的集成测试,那么你需要一个自动浏览网站并验证输出信息的正确性.这是web测试的工作.
在SimpleTest中进行web测试相当原始.没有JavaScript.不过其他的大多数操作都能被模拟出来.
这里有一个琐碎的例子可供参考.一个主页被打开,我们从这里点击访问"about"页,然后测试客户端检测到的内容.
<?php require_once(‘simpletest/autorun.php’); require_once(‘simpletest/web_tester.php’);
class TestOfAbout extends WebTestCase { function testOurAboutPageGivesFreeReignToOurEgo() { $this->get(‘http://test-server/index.php’); $this->click(‘About’); $this->assertTitle(‘About why we are so great’); $this->assertText(‘We are really great’); } } ?>
你也可以操作表单…
<?php require_once(‘simpletest/autorun.php’); require_once(‘simpletest/web_tester.php’);
class TestOfRankings extends WebTestCase { function testWeAreTopOfGoogle() { $this->get(‘http://google.com/'); $this->setField(‘q’, ‘simpletest’); $this->click(“手气不错”); $this->assertTitle(‘SimpleTest - Unit Testing for PHP’); } } ?> 淡水的标题是《Simple Test 中文参考》,其实下面的那点东西才是中文参考,上面的算是快速入门吧…
下面是一些方法的中文介绍
<?php UnitTestCase($label = false);//设置测试标题的如果为空自动获取当前类的名称来做标题!
setUp();//测试开始的时候自动运行
tearDown();//测试结束后自动运行
assertTrue($result, $message = false);//测试$result是否为True, 如果是True则通过测试
assertFalse($result, $message = ‘%s’);//测试$result是否为False, 如果是False则通过测试
assertNull($value, $message = ‘%s’);//测试$value的值是否为Null, 如果是Null则通过测试.
assertNotNull($value, $message = ‘%s’);//测试$value的值是否为Null, 如果不是Null则通过测试.
assertIsA($object, $type, $message = ‘%s’);//测试$object 对象是不是$type类的实例, 如果是则通过测试.
assertNotA($object, $type, $message = ‘%s’);//测试$object 对象是不是$type类的实例, 如果不是则通过测试.
assertEqual($first, $second, $message = ‘%s’);//测试$first是否等于$second, 如果等于则通过.
assertNotEqual($first, $second, $message = ‘%s’);//测试$first是否等于$second, 如果不等于则通过.
assertWithinMargin($first, $second, $margin, $message = ‘%s’);//测试$first减去$second的绝对值是否小于$margin, 如果小于则通过.
assertOutsideMargin($first, $second, $margin, $message = ‘%s’);//测试$first减去$second的绝对值是否大于$margin, 如果大于则通过.
assertIdentical($first, $second, $message = ‘%s’);//测试$first和$second类型是否一样, 如果一样则通过测试.
assertNotIdentical($first, $second, $message = ‘%s’);//测试$first和$second类型是否一样, 如果不一样则通过测试.
assertReference(&$first, &$second, $message = ‘%s’);//测试$first是否为$second的引用, 如果是则通过测试.
assertClone(&$first, &$second, $message = ‘%s’);//测试$first是否为$second完全的副本, 如果是则通过测试.
assertPattern($pattern, $subject, $message = ‘%s’);//测试$subject里面是否出现$pattern, 如果出现则通过, $pattern可以使用正则表达式.
assertNoPattern($pattern, $subject, $message = ‘%s’);//测试$subject里面是否出现$pattern, 如果没出现则通过, $pattern可以使用正则表达式.
最后来个例子: <?php
require_once ‘simpletest/unit_tester.php’; require_once ‘simpletest/reporter.php’;
class DbTest extends UnitTestCase {
public function setUp() {
parent::UnitTestCase("SimpleTest Title");
}
public function testAssertTrue() {
$a = true; //这里赋值为true
$this->assertTrue($a); //判断是否为true
}
public function testAssertFalse() {
$a = false; //这里赋值为false
$this->assertFalse($a); //判断是否为false
}
public function testAssertNull() {
$a = null; //将$a赋值为null;
$this->assertNull($a); //判断$a是否为null
}
public function testAssertNotNull() {
$a = "asdf"; //这里赋值为asdf
$this->assertNotNull($a); //判断$a是否为null, 如果不是则通过
}
public function testAssertIsA() {
$a = new mysqli(); //实例化一个mysqli对象
$this->assertIsA($a, 'mysqli'); //判断$a是否为mysqli类的实例
}
public function testAssertNotA() {
$a = new mysqli(); //实例化一个mysqli对象
$this->assertNotA($a, 'asdfadfadsfadsf'); //判断$a是否为mysqlis类的实例
}
public function testAssertEqual() {
$a = true; //这里将$a赋值为1
$this->assertEqual($a, true); //判断$a是否为1
$a = false;
$this->assertEqual($a, false); //判断$a是否为1
}
public function testAssertNoEqual() {
$a = false; //这里将$a赋值为false
$this->assertNotEqual(true, $a); //判断$不等于false
$a = true;
$this->assertNotEqual(false, $a); //判断$不等于false
}
public function testAssertWithinMargin() {
$this->assertWithinMargin(25, 25, 5); //判断25-25的绝对值是否小于5
$this->assertWithinMargin(25, 25, 0); //判断25-25的绝对值是否小于于0
$this->assertWithinMargin(25, 22, 5); //判断25-22的绝对值是否小于5
}
public function testAssertOutsideMargin() {
$this->assertOutsideMargin(31, 25, 5); //判断31-25的绝对值是否大于5
$this->assertOutsideMargin(26, 25, 0); //判断26-25的绝对值是否大于0
$this->assertOutsideMargin(27, 20, 5); //判断27-20的绝对值是否大于5
}
public function testAssertIdentical() {
$this->assertIdentical(0, 0); //判断0和0类型是否一样
$this->assertIdentical("test", "test"); //判断"test"和"test"类型是否一样
}
public function testAssertNotIdentical() {
$this->assertNotIdentical(0, false); //判断0和false类型是否一样
$this->assertNotIdentical("test", "tess"); //判断"test"和"tess"类型是否一样
}
public function testAssertReference() {
$a = "test";
$b = &$a; //这里引用$a
$this->assertReference($a, $b); //判断$b是否引用$a
}
public function testAssertPattern() {
$this->assertPattern('/hello/i', 'Hello world');
}
public function testAssertNoPattern() {
$this->assertNoPattern('/123/i', 'Hello world');
}
}
$test = new DbTest(); $test->run(new HtmlReporter());
最后修改于 2012-06-25