php单元测试使用示例(转载)

转载自:http://www.javaranch.com/unit-testing.jsp,原文标题《Evil Unit Testing》,原作者:Paul Wheaton
转载自: http://www.cnblogs.com/harlanc/p/6838155.html ,原文标题《单元测试有毒》翻译者:HarlanC
我稍做了修改。

作者的核心观点是:绝大部分的项目,应该做的测试是,单元测试70%,其余才是功能测试和集成测试。单元测试是基础。
作者的观点:
单元测试是可测试代码的最小的一部分。通常是一个单一的方法,不会使用其它方法或者类。非常快!上千个单元测试能够在10秒以内跑完!单元测试永远不会使用:

  •     数据库
  •     一个app服务器(或者任何类型的服务器)
  •     文件/网络 I/O或者文件系统
  •     另外的应用
  •     控制台(System.out,system.err等等)
  •     日志
  •     大多数其他类(但不包括DTO‘s,String,Integer,mock和一些其他的类)


单元测试几乎总是回归测试套件(regression suite)的一部分。

下面是单元测试使用示例:
假设被测类FarmServlet如下:

//原被测类
public class FarmServlet extends ActionServlet
{ 
        public function doAction( ServletData $servletData ) 
        {
            $species = $servletData->getParameter("species");
            $buildingID = $servletData->getParameter("buildingID");
            if ( Str::usable( $species ) && Str::usable( $buildingID ) )
            {
                $remote = FarmEJBUtil::getHome()->create();
                $remote->addAnimal( $species , $buildingID );
            }
        } 
}

这里不仅仅调用了其他业务逻辑,还调用了应用服务器!可能还会访问网络!上千次的调用可能会花费不少于10秒的时间。另外对$remote的修改可能会破坏我对这个方法的测试!所以我们需要引入一个mock对象。

首先是创建mock。如果$remote是一个对象,我将会继承(extend)它并且重写(override)或实现(implement)所有的方法。

//远程服务对象mock类,
//新写的第一个类,为了单元测试
public class MockRemote 
{
        $addAnimal_species = null;
        $addAnimal_buildingID = null;
        $addAnimal_calls = 0;
        public function addAnimal( $species, $buildingID )
        {
            $this->addAnimal_species = $species ;
            $this->addAnimal_buildingID = $buildingID ;
            $this->addAnimal_calls++;
        }
}


这个MockRemote类什么都没做,只是携带了单元测试和需要被测试代码之间要交互的数据。

现在我需要使用mock代码来替代调用应用服务器的部分。我对需要使用mock的地方做了注释:

首先,被测类FarmServlet中,让我们把这句代码从其他猛兽中分离出来:

//修改后的原被测类,为了单元测试,以及代码解耦。
public class FarmServlet extends ActionServlet
{
        // 新加公共的成员方法,可以让测试类注入或通过继承修改。
        public function getRemote()  
        { 
            return FarmEJBUtil::getHome()->create(); 
        }

        public function doAction( ServletData $servletData ) 
        {
            $species = $servletData->getParameter("species");
            $buildingID = $servletData->getParameter("buildingID");
            if ( Str::usable( $species ) && Str::usable( $buildingID ) )  {
                $remote = $this->getRomote();  // 改成从公共成员方法中获取对象
                $remote->addAnimal( $species , $buildingID );
            }
         }
}


前面已经新写了一个mock类,现在再写一个类FarmServletShunt继承被测类,然后对继承的这个类做测试,成功即认为原先的被测类功能正确。
//该类FarmServletShunt继承被测类,测试用
// 新写的第2个类
class FarmServletShunt extends FarmServlet
{ 
        puhblic $getRemote_return; // 新加成员变量,好注入
        public function getRemote() // 覆盖了被测类的获得远程服务的方法,实现注入
        {
            return getRemote_return;
        }
}

注意一下怪异的名字:“shunt”。我不确定它是什么意思,但我认为这个词语来自电子工程/工艺,它指用一段电线来临时组装一个完整的电路。一开始听起来这个想法很愚蠢,但是过后我就慢慢习惯了。

一个shunt有点像mock,一个没有重写所有方法的mock。用这种方法,你可以mock一些方法,然后测试其他的方法。一个单元测试可以由几个shunts来完成,它们重写了相同的类,每个shunt测试了类的不同部分。Shunt通常情况下为嵌套类。


再对ServletData做mock,方便注入。

//对ServletData类或接口做mock
//新写的第3个类,为了单元测试
public class MockServletData 
{

        private $arr;

        public function __construct($arr){

            $this->arr = $arr;

        }

        public function getParameter( $parameter )
        {
            return $this->arr[$paramter];
        }
}

终场表演的时候到了!看一下单元测试代码!

// 这是最后写的测试文件,
public class TestFarmServlet extends TestCase
{
        public function testAddAnimal() {
            $mockRemote = new MockRemote();//定义mock的远程服务对象
            $shunt = new FarmServletShunt();//该类继承被测类,拿它测试
            $shunt->getRemote_return = $mockRemote; //注入远程服务对象
            // 另一个mock对象
            $mockServletData = new MockServletData([      // 定义mock的ServletData对象
                "species" => "dog",
                "buildingID" => 27,
            ]); 

            // 执行被测方法
            $shunt->doAction( $mockServletData );

            // 断言原被测类中,远程对象使用了一次addAnimal方法
            $this->assertEquals( 1 , $mockRemote->addAnimal_calls );

            //个人感觉下面两句不重要。因为这里的逻辑其实是mock里的逻辑。
            $this->assertEquals( "dog" , $mockRemote->addAnimal_species );
            $this->assertEquals( 27 ,    $mockRemote->addAnimal_buildingID );
        }
}

基本的测试框架我们就展示完了。下面我要和大家分享一个和单元测试有关的概念——依赖注入,也是我们的单元测试中要到的,敬请期待。

猜你喜欢

转载自xieye.iteye.com/blog/2384439