欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何进行单元测试(二)

程序员文章站 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方法以及测试了
            .................
     }
} 

 这种方法要求我们在代码设计时遵循“面向接口编程”的原则。