laravel单元测试学习笔记

1) 异常测试

    使用背景:为了使业务逻辑更严谨,在bll文件中经常会有各种if else语句并抛异常,而在写对应的单元测试时,我们要考虑到每种情况,并试图提高覆盖率。

    方法:使用setExpectedException()方法来设定所预期的异常。

    例:测试gold/backend/app/Bll/Zc/House.php的changeRoom方法          

              /**

* @dataProvider providerOverride
*/
public function testChangeRoom($userId, $houseId, $targetRoomId, $exception)
{
         $noneChange = $this->bllHouse->changeRoom(45520, 1, 2); //不用更换
         $this->assertTrue($noneChange, 'Change Room Failed.');
         $changeSuccess = $this->bllHouse->changeRoom(45520, 1, 1); //更换成功
         $this->assertTrue($changeSuccess, 'Change Room Failed.');
         $this->setExpectedException($exception);
         $this->bllHouse->changeRoom($userId, $houseId, $targetRoomId);
}

public function providerOverride()
{
         return array(
               array(45520, 1, 3, 'Gold\Exceptions\SystemException\ZcException'),//房产不可选
               array(45520, 1, 4, 'Gold\Exceptions\SystemException\ZcException'),//无此目标房产
               array(440, 1, 1, 'Gold\Exceptions\SystemException\ZcException'),//无对应用户房产信息
          );

}

          针对bll文件中的changeRoom方法,每种情况的异常测试都已覆盖到,去查看覆盖率时,你会发现并不是100%。

          原因:是在捕获到预期的异常时,测试便不会继续运行,故if语句的‘}’总会显示未被覆盖(如下图示)。

          解决办法:在改语句后面加上“@codeCoverageIgnore”,把改行忽略掉。(为了率概率,有种作弊的感觉,有木有。。。)

          另外,对于非常简单的方法,比如方法中只有一条return语句,并返回常量,可以不写单测,直接在方法的注释上面加上“@codeCoverageIgnore”,这样在统计覆盖率时就会忽略该方法。

         

         

 

2)mockery的使用(文档链接:http://docs.mockery.io/en/latest/reference/startup_methods.html

     为什么要mock?

       隔绝其他模块出错引起本模块的测试错误。
       隔绝其他模块的开发状态,只要定义好接口,不用管他们开发有没有完成。
       一些速度较慢的操作,可以用Mock Object代替,快速返回。
       对于分布式系统的测试,使用Mock Object会有另外两项很重要的收益:
       通过Mock Object可以将一些分布式测试转化为本地的测试
       将Mock用于压力测试,可以解决测试集群无法模拟线上集群大规模下的压力

     a)mock某class的静态方法

        Mockery::mock('alias:Gold\Model\Payment')->shouldReceive('someMethod')->andReturn('something');

        alias官方解释:

        Alias mocks create a class alias with the given classname to stdClass and are generally used to enable the mocking of public static methods. 


     b) 使用背景:在测试时,需要考虑到各种情形,有时会需要模拟同一函数,相同参数,但每次请求时返回不同的结果。

         例:模拟获取支付中心支付流水号的情形。

               $mock = \Mockery::mock('alias:Gold\Model\Payment');

               $mock->shouldReceive('getOrderNum')
                        ->with(config("payment.app.zc.businessId"))
                        ->andReturn('20151010100', '20151010222')
;

          andReturn中列出了两个参数,可以理解为第一次模拟的结果是20151010100,第二次是20151010222

          

     c) 使用背景:当待测方法中有调用某个类的常量和static方法时,若直接alias mock该类,是取不到对应的const的,同时运行单测后会报类似错误:

         Fatal error: Undefined class constant 'SMS_TYPE_PAY' in /data1/www/laravel/cloud/zhangfangfang/gold/backend/app/Bll/Zc/Phase/Three.php on line 130

         例:众筹多阶段测试Tests/Zc/Bll/Phase/TwoTest.php

         解决办法

                class HfbConstMock
                {
                     const SMS_TYPE_TRANSIN = 138;
                     const SMS_TYPE_PAY = 139; 
                }

 

$mock = M::namedMock('Gold\Model\Hfb', 'HfbConstMock');
$mock->shouldReceive('pay')->andReturn($andReturn);

dd(Gold\Model\Hfb::SMS_TYPE_TRANSIN); //输出138


     d)使用背景:当多个test文件都alias/instance mock了同一个class,有可能会报如下错误:

            Fatal error: Cannot redeclare Mockery_ *

        解决办法(鉴于翻译水平有限,此处直接引用文档中的原话):

           Using alias/instance mocks across more than one test will generate a fatal error since you can’t have two classes of the same name. To avoid this, run each test of this kind in a separate PHP process (which is supported out of the box by both PHPUnit and PHPT).


 3) PDOException: SQLSTATE[HY000] [1040] Too many connections. 当跑众筹下面所有测试时,报了上面的错误。

       解决办法

             gold/backend/tests/TestCase.php中的teardown方法中加了如下代码:

foreach (\DB::getConnections() as $name => $connection) {
         \DB::purge($name);
}

             这样在每个测试用例运行结束后都会disconnect所有的数据库连接。

 

4)@depends使用注意事项

      phpunit运行时是按照编码顺序进行的,@depends依赖关系要PHPer自己控制。若testB依赖testA,则testA必须在testB前面。

      示例: 

public function testGetPayAmount()
{
     $res = $this->bllPhaseThree->getPayAmount(45520, 3);
     $this->assertEquals(20100000, $res);
     return 20100000;
}
/**
* @depends testGetPayAmount
*/
public function testCreateOrder($payAmount)
{

    $Param['iAmount'] = $payAmount;

    .......

}

     上例中,若testGetPayAmount方法写在了testCreateOrder方法后面,则运行结果就会显示为S.。跳过的原因是testCreateOrder找不到依赖的testPayAmount方法。


5)@dataProvider使用

      提供数据的方法与test方法没有先后关系。

      a)沿用@depends中的示例,简单描述为,若testB依赖testA,且testB同事需要接受dataC中的数据。要注意dataC中的数据要优先传入testB!否则会报错!

      示例:

public function testGetPayAmount()
{
     $res = $this->bllPhaseThree->getPayAmount(45520, 3);
     $this->assertEquals(20100000, $res);
     return 20100000;
}
/**
* @depends testGetPayAmount

* @dataProvider createDataProvider
*/
public function testCreateOrder($entity, $exception, $payAmount)      //注意参数的先后顺序~~

{

        $param['iAmount'] = $payAmount;

        ......

                       $this->setExpectedException($exception);
                       $this->bllPhaseThree->createOrder($entity);

}

public function createDataProvider()

{

         return array(

                 array('***', '******'),

          );

}


6)基境相关 //Better solution will be appreciated.

      当前众筹单测的seed调用都是写在测试类的setUp()方法中,而每个测试方法运行时都会执行一次setUp()和tearDown()。但有时我们需要同一个测试类中所有测试方法共用一个基境中已被修改的数据。

      解决办法:

class ZcBllThreeTest extends TestCase
{
       public static $setUpPhaseThreeCrowd = false;
       public function setUp()
       {
             parent::setUp();

             ....
             if (!self::$setUpPhaseThreeCrowd) {
                    $this->changePhaseCrowdToThree();
                    self::$setUpPhaseThreeCrowd = true;
              }
        }
        public function changePhaseCrowdToThree()
        {
              $data = [
                      "iAutoID" => 3,
                      "iCurrentPhase" => 3,
               ];
              $entity = new CrowdEntity($data);
              $this->bllCrowd->save($entity);
         }

         }

其实,应该是可以直接调用phpunit中的setUpBeforeClass()。But How??


7) 若待测方法有从cookie中获取相关内容,对此类型方法进行测试。

    最初思路:在testMethod中setcookie,总是会报如下错误: ErrorException: Cannot modify header information - headers already sent by (output started atphar:///usr/local/bin/phpunit/phpunit/Util/Printer.php:139)

    解决办法:直接赋值。参考 gold/backend/tests/Zc/Bll/GatewayTest.php:58

 public function testGetPartNoFromCookie()
{
         $res = $this->bllGateway->getPartNoFromCookie();
         $this->assertEmpty($res);
         $value = urldecode(

                'a%3A4%3A%7Bs%3A3%3A%22sIP%22%3Bs%3A11%3A%2210.59.90.91%22%3Bs%3A5%3A%22sType%22%3BN%3Bs%3A7%3A%22sSource%22%3Bs%3A5%3A%22jrqjd%22%3Bs%3A7%3A%22sPartNo              %22%3Bs%3A4%3A%221234%22%3B%7D'
          );
        $_COOKIE[CrowdConst::SOURCE_COOKIE_NAME] = $value;
        $resJRD = $this->bllGateway->getPartNoFromCookie();
       $this->assertEquals('1234', $resJRD);
}

8)phpunit --process-isolation test.php      

     Run each test in a separate PHP process.

     注:这个执行语句会使得单测的运行时间加倍, because for each single test, a new PHP instance is started, the bootstrap is executed etc.

     可以使用@runTestsInSeparateProcesses,@runInSeparateProcess来改进。

     @runTestsInSeparateProcesses 指明测试类内所有测试各自运行在独立的PHP进程中。常常需要与 @preserveGlobalState disabled搭配使用。

     @runInSeparateProcess 指明某个测试要运行在独立的 PHP 进程中

你可能感兴趣的:(laravel,php,&,golang)