如何进行单元测试(二)
程序员文章站
2022-06-05 12:11:58
...
场景一描述: 项目代码比较复杂,数据可能从数据库获取,也可能从其他服务里获取,构造和维护这些数据比较困难。
伪代码示例:
// 我把被测试的类命名为TestedClass, 单测的类命名为TestClass
class TestedClass{
// 被测试的函数命名为testedMethod, 测试的方法命名为testMethod
public function testedMethod() {
// 此处省略了其他代码
......................
// 从数据库里获取数据
$sql = "select userid from Main.useracct where role=1.......";
$result = DB::getInstance()->fetchAll($sql);
// 此处省略了其他代码
......................
}
}
// 数据库对象
class DB {
...........
public static function getInstance() {
................
}
...........
}
分析:对TestedClass的testedMethod进行单测时,我们需要从数据库里获取数据然后进行一系列运算。而数据库里的数据可能被其他代码更改,因此在单测时可能无法得到稳定的数据。但是自动化单元测试要求每个测试都是可重复运行的,因此我们需要寻找解决方法。这个场景的解决方法很多,下面描述三种。
方法1:对代码设计进行一些改造,对外部环境的访问使用protected 函数包装。在例子中这么做:
class TestedClass {
public function testedMethod() {
......................
// 从数据库里获取数据
$sql = "select userid from Main.useracct where role=1.......";
$result = $this->_fetchData($sql);
......................
}
protected function _fetchData($sql) {
return DB::getInstance()->fetchAll($sql);
}
}
然后在单测testedMethod时,对_fetchData函数进行Mock,因此避开了从外部环境获取数据的问题。这种方法在很多情况下都能使用,但是不能滥用。如果一个类里面充斥着这种包装,我们就得考虑这个类是否是单一职责的,与外部环境的沟通方式是否能抽取共性,进而进行代码重构了。
方法2: 每个单元测试维护自己需要的环境,保持测试前后外部环境不受影响
对于例子而言,单元测试可以这么写:
class TestClass extends PHPUnit_Framework_TestCase {
public function setUp() {
// 启动事务
//beginTransaction();
}
public function tearDown() {
// 回滚事务,恢复测试之前的环境
// rollBackTransaction();
}
public function test_testedMethod() {
// 清空Main.useracct表 =》如果useracct中数据较多,此处的执行将很慢
// 在useracct中插入数据
// 调用testedMethod
.....................
}
}
这种方法利用了数据库的事务回滚特性,但是在有些情况下构造数据需要耗费较多的时间,执行性能上可能比较差。
方法3: 代码设计时对外部环境进行隔离,测试代码使用虚拟外部环境,不访问真实环境。
对于例子而言,代码做如下改造:
interface DBInterface {
public function fetchAll($sql);
}
class TestedClass {
protected $_dbInterface;
public function __construct() {
$this->_dbInterface = DB::getInstance();
}
public function setDbInterface($dbInterface) {
$this->_dbInterface = $dbInterface;
}
public function testedMethod() {
.............
$result = $this->_dbInterface->fetchAll($sql);
.............
}
}
class DB implements DbInterface() {
public function fetchAll($sql) {
...........
}
}
单元测试为:
class TestClass extends PHPUnit_Framework_TestCase {
public function test_testedMethod() {
$testedClass = new TestedClass();
$mockDb = $this->getMock("DbInterface".....);
$testedClass->setDbInterface($mockDb);
// 接下来就是mock方法以及测试了
.................
}
}
这种方法要求我们在代码设计时遵循“面向接口编程”的原则。
上一篇: Java单元测试