如何进行单元测试(三)
场景二描述:测试代码里调用了其他类的方法,而其他类的方法逻辑复杂,需要构造和维护很复杂的数据进行测试。
伪代码示例:
class TestedClass {
public function testedMethod() {
// ......
// 调用了外部类的方法
$foreignObject = new ForeignClass();
$result = $foreignObject->complicatedMethod();
// ......
}
}
分析:场景二和场景一不同的地方在于,外部环境一般是比较可靠的,只要按照外部环境要求的方式调用,我们就能得到正确的结果,不需要对外部环境进行单测; 这个问题描述的是被测试类TestedClass有关联类ForeignClass,我们需要决定采用深度测试还是平面测试。
深度测试的含义为测试testedMethod时,ForeignClass的complicatedMethod将被执行;ForeignClass里的complicatedMethod如果调用了其他类的其他方法,其他方法也将被执行。由此下去,一次测试可能涉及的类 和方法就特别多,排查错误将会比较麻烦。采用这种方式时,一般推荐采用开发ForeignClass代码->单测ForeignClass代码 ->开发TestedClass->单测TestedClass的步骤(此处不关心TDD是先开发,后测试还是先测试,后开发,我们以后再讨 论这个问题)。
平面测试的含义为测试testedMethod时,对ForeignClass的complicatedMethod不予实际调用,相信这个类的这 个方法能按约定完成任务,而在对ForeignClass的complicatedMethod单测时保证代码正确性。这种方式下测试的目的非常明确,易 于构造test case,甚至在ForeighClass还未完全实现前就能进行单元测试。我比较推荐使用平面测试的方法。
对于例子,场景一的方法1可以拿过来使用,方法2无法使用,方法3也可以使用。除此之外,对于PHP语言而言,我们还可以使用runkit扩展的runkit_method_redefine方法,该方法能够动态地改变函数实现。做法如下:
方法4:
class TestClass extends PHPUnit_Framework_TestCase {
public function test_testedMethod() {
// 重新定义ForeignClass的foreignMethod函数
$result = array(....);
TestHelper::getInstance()->setResult($result);
runkit_method_redefine("ForeignClass", "foreignMethod", "", 'return TestHelper::getInstance()->getResult();'
$testedClass = new TestedClass();
$result = $testedClass->testedMethod();
........
}
}
class TestHelper {
protected $_result;
public static function getInstance() {
..................
}
public function setResult($result) {
$this->_result = $result;
}
public function getResult() {
return $this->_result;
}
}
这种方法利用PHP函数的实现机制来提供扩展支持,对于非PHP语言而言不一定有用。