Php单元测试工具simpletest(二)
 

2009-08-13 来源:网络

 

一、分组测试

分组测试

运行测试用例作为分组的一部分,测试用例应该被放在没有运行码的文件中:

<?php 
    require_once('../classes/io.php'); 
    class FileTester extends UnitTestCase { 
        ... 
    } 
    class SocketTester extends UnitTestCase { 
        ... 
    } 
?> 

尽可能多的用例出现在同一个文件中。他们将包含任何他们需要的代码,包括需要测试的库,但是不包括simpletest库。

如果你已经继承任何测试用例,在php 4中,你能这么使用:

<?php
    require_once('../classes/io.php');
    class MyFileTestCase extends UnitTestCase { 
        ... 
    } 
    SimpleTest::ignore('MyFileTestCase');   
    class FileTester extends MyFileTestCase { ... }
    class SocketTester extends UnitTestCase { ... }
?>

Ignore告诉测试系统,忽略他的执行。

在php 5中,可以用abstract关键字:

abstract class MyFileTestCase extends UnitTestCase {
    ...
}
class FileTester extends MyFileTestCase { ... }
class SocketTester extends UnitTestCase { ... }

下一步,我们创建组测试文件,叫做my_group_test.php。

我们用安全方法添加测试文件:

<?php
    require_once('simpletest/unit_tester.php');
    require_once('simpletest/reporter.php');
    require_once('file_test.php');   
    $test = &new GroupTest('All file tests'); 
    $test->addTestCase(new MyFileTestCase()); 
    $test->run(new HtmlReporter()); 
?>

这将在套餐运行之前,实例化测试用例,可以不用逐个去实例化测试用例。

也能按照下面的方法来做:

<?php
    require_once('simpletest/unit_tester.php');
    require_once('simpletest/reporter.php');
    $test = &new GroupTest('All file tests');
    $test->addTestFile('file_test.php'); 
    $test->run(new HtmlReporter());
?>

这儿有2件事情容易产生错误,需要注意:

1. 这个测试文件可能已经被php解析,因此新类将不能被增加。你应该确认测试用例只包含在这个文件中,不在其他文件中。

2.要注意ignore的使用,并且自己派生的测试用例类必须是抽象类,而且必须在GroupTest::addTestFile之前调用ignore。

高级分组

上面的方法是放所有测试用例进一个大的文件。对于大型项目来讲,这有点不够灵活,你可能需要按照某种排序方式分组。

为了获得更大的灵活性,你可以子类化GroupTest,然后按照需要实例化:

<?php
    require_once('simpletest/unit_tester.php');
    require_once('simpletest/reporter.php');
    class FileGroupTest extends GroupTest { 
        function FileGroupTest() { 
            $this->GroupTest('All file tests'); 
            $this->addTestFile('file_test.php'); 
        } 
    } 
?>

现在我们能从分离的运行文件中调用测试:

<?php
    require_once('file_group_test.php');
    $test = &new FileGroupTest(); 
    $test->run(new HtmlReporter()); 
?>

或者我们可以对用例进行分组后,进入一个大的分组。我们能自由的混合分组或单个单元测试,只要我们注意双重包含的问题。

<?php
    $test = &new BigGroupTest('Big group'); 
    $test->addTestFile('file_group_test.php'); 
    $test->addTestFile('some_test_case.php'); 
    $test->run(new HtmlReporter());
?>

二、Mock对象

什么是mock对象?

单独测试一个类或方法里的代码,而不测试里面调用的其他类或方法的代码。即假定调用的其他类或方法都正常执行。

使用Mock Object的场合

实际对象的行为还不确定。

实际的对象创建和初始化非常复杂。

实际对象中存在很难执行到的行为(如网络异常等)。

实际的对象运行起来非常的慢。

实际对象是用户界面程序。

实际对象还没有编写,只有接口等。

创建mock对象

方法与创建服务器存根相同,我们需要的所有事情就是一个已经存在的类。假如说有下面这个数据库类:

class DatabaseConnection { 
    function DatabaseConnection() { 
    } 
    function query() { 
    } 
    function selectQuery() { 
    } 
} 

这个类没有实际实现代码。为了创建这个类的mock版本,我们需要包含mock类库,然后运行生成器:

require_once('simpletest/unit_tester.php'); 
require_once('simpletest/mock_objects.php'); 
require_once('database_connection.php'); 
Mock::generate('DatabaseConnection'); 

这生成了一个名为MockDatabaseConnection的克隆类。我们现在能在我们的测试用例中,实例化这个新类。

require_once('simpletest/unit_tester.php');
require_once('simpletest/mock_objects.php');
require_once('database_connection.php');
Mock::generate('DatabaseConnection');
class MyTestCase extends UnitTestCase { 
    function testSomething() { 
        $connection = &new MockDatabaseConnection(); 
    } 
} 

作为操作者的mock

一个类的mock版本拥有所有方法,因此像$connection->query()这样的调用仍然是合法的。返回值是null,但是我们需要改变他:

$connection->setReturnValue(‘query’, 37)

现在任何时候我们调用$connection->query(),他的返回值都是37。我们能设置任何东西的返回值,一个数据库的hash或者一个持久化对象,都可以。参数在这儿是不相关的,我们总是能得到同样的返回值。

我们也能添加附加的方法到mock对象,也可以选择我们自定义的类名作为mock对象的类名。

Mock::generate('DatabaseConnection', 'MyMockDatabaseConnection', array('setOptions')); 

这儿setOptions()方法被增加。

事情不总是那么简单。迭代就是一个通常的问题,如果在测试时总是返回同一个值,会引起死循环。因此我们需要建立一些值的序列。我们来看看下面的简单的迭代的例子:

class Iterator {
    function Iterator() {
    }
    function next() {
    }
}

这是我们能设想的最简单的迭代。假设迭代总是返回字符串直到到达尾部(当他返回false时),我们能用下面的方法仿真:

Mock::generate('Iterator');
class IteratorTest extends UnitTestCase() {
    function testASequence() {
        $iterator = &new MockIterator(); 
        $iterator->setReturnValue('next', false); 
        $iterator->setReturnValueAt(0, 'next', 'First string'); 
        $iterator->setReturnValueAt(1, 'next', 'Second string'); 
        ...
    }
}

另外一个需要小技巧的情况是重载get()方法。下面是一个保存名-值对的例子:

class Configuration {
    function Configuration() {
    }
    function getValue($key) {
    }
}

这种基本配置往往随着机器的变化而变化,我们可以手工直接配置。虽然所有的数据都来自getValue方法,但是我们需要根据键返回不同的值。很幸运,mock提供这样的过滤系统:

$config = &new MockConfiguration(); 
$config->setReturnValue('getValue', 'primary', array('db_host')); 
$config->setReturnValue('getValue', 'admin', array('db_user')); 
$config->setReturnValue('getValue', 'secret', array('db_password')); 

然后我们可以这样调用:

$config->getValue('db_user')

他将返回“admin”。附加的参数,将尝试和前面的参数进行匹配。

$config->setReturnValue('getValue', false, array('*')); 

$config->setReturnValue('getValue', false); 

是不一样的。第一种情况接受任何单个的参数,第二种情况接受任何数量的参数。 

有三种因素可以被同时使用,时间、参数、是否copy:

$complex = &new MockComplexThing();
$stuff = &new Stuff();
$complex->setReturnReferenceAt(3, 'get', $stuff, array('*', 1)); 

它将返回$stuff,只有在第三次调用,并且只有在第二个参数设置为1的时候。

最后一个技巧是,一个对象创建另外一个对象,也就是通常说的工厂模式。假设成功调用query后,返回一个记录集,可以通过next不断迭代获取值,直到返回false为止。这听起来有点像恶梦,实际上mock能做这样的事情。

看下面:

Mock::generate('DatabaseConnection');
Mock::generate('ResultIterator');
class DatabaseTest extends UnitTestCase {
    function testUserFinder() {
        $result = &new MockResultIterator(); 
        $result->setReturnValue('next', false); 
        $result->setReturnValueAt(0, 'next', array(1, 'tom')); 
        $result->setReturnValueAt(1, 'next', array(3, 'dick')); 
        $result->setReturnValueAt(2, 'next', array(6, 'harry')); 
        $connection = &new MockDatabaseConnection(); 
        $connection->setReturnValue('query', false); 
        $connection->setReturnReference( 
                'query', 
                $result, 
                array('select id, name from users')); 
        $finder = &new UserFinder($connection);
        $this->assertIdentical(
                $finder->findNames(),
                array('tom', 'dick', 'harry'));
    }
}

三、部分mock

参看:http://www.lastcraft.com/partial_mocks_documentation.php

四、输出报告

Simpletest非常好的遵循mvc模式。报告类是显示层。

在html中输出结果

详查HtmlReporter类的使用。

命令行输出结果

详查TextReporter类

<?php
    require_once('simpletest/unit_tester.php');
    require_once('simpletest/reporter.php');
    $test = &new GroupTest('File test');
    $test->addTestFile('tests/file_test.php');
    $test->run(new TextReporter());
?>

远程测试

SimpleTestXmlParser

<?php
    require_once('simpletest/xml.php');
    ...
    $parser = &new SimpleTestXmlParser(new HtmlReporter());
    $parser->parse($test_output);
?>

五、期望

六、Web测试器

参看:http://www.lastcraft.com/web_tester_documentation.php

七、测试表单

参看:http://www.lastcraft.com/form_testing_documentation.php

八、验证

参看:http://www.lastcraft.com/authentication_documentation.php

九、可执行脚本的浏览器

参看:http://www.lastcraft.com/browser_documentation.php


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织