1. 概念

什么是基境

首先参考翻译原文

在编写测试时,最费时的部分之一是编写代码来将整个场景设置成某个已知的状态,并在测试结束后将其复原到初始状态。这个已知的状态称为测试的 基境(fixture)

简单地说,就是为了测试某个方法/函数所需要的环境前提。那么基境就是针对某个测试而言的,其所需要的初始状态。下面举个例子:

  1. <?php
  2. use PHPUnit\Framework\TestCase;
  3. class StackTest extends TestCase
  4. {
  5. public function testEmpty()
  6. {
  7. $stack = [];
  8. $this->assertEmpty($stack);
  9. return $stack;
  10. }
  11. /**
  12. * @depends testEmpty
  13. */
  14. public function testPush(array $stack)
  15. {
  16. array_push($stack, 'foo');
  17. $this->assertEquals('foo', $stack[count($stack)-1]);
  18. $this->assertNotEmpty($stack);
  19. return $stack;
  20. }
  21. /**
  22. * @depends testPush
  23. */
  24. public function testPop(array $stack)
  25. {
  26. $this->assertEquals('foo', array_pop($stack));
  27. $this->assertEmpty($stack);
  28. }
  29. }
  30. ?>

在这个例子中,testEmpty的没有基境的需求,基础环境随意;testPush由于依赖的缘故,需要一个空的$stack;testPop依赖于testPush,即需要一个有内容的$stack。

2. 共享基境

有哪些方法

  • 每个测试方法的前后
    • setUp()
    • tearDown()
  • 第一个测试之前,与最后一个测试之后
    • setUpBeforeClass()
    • tearDownAfterClass()

      例子

      ```php <?php use PHPUnit\Framework\TestCase;

class StackTest extends TestCase { protected $stack;

  1. protected function setUp()
  2. {
  3. $this->stack = [];
  4. }
  5. public function testEmpty()
  6. {
  7. $this->assertTrue(empty($this->stack));
  8. }
  9. public function testPush()
  10. {
  11. array_push($this->stack, 'foo');
  12. $this->assertEquals('foo', $this->stack[count($this->stack)-1]);
  13. $this->assertFalse(empty($this->stack));
  14. }
  15. public function testPop()
  16. {
  17. array_push($this->stack, 'foo');
  18. $this->assertEquals('foo', array_pop($this->stack));
  19. $this->assertTrue(empty($this->stack));
  20. }

} ?>

  1. 与上面第一个例子形成对比的,该例子使用了setUp()函数为每个测试方法建立了共享的基境,而没有使用依赖。但是同时也在各个方法中自行设置了所需的基境。
  2. <a name="aVrz8"></a>
  3. ## 关于setUp()与tearDown()
  4. 理论上说,他俩应该是对称的。但实践中,只有在setUp中分配了文件、套接字之类的外部资源才需要tearDown。或者是在setUp中创建了大量对象,可以在tearDownunset掉。
  5. <a name="L8r0d"></a>
  6. ## 关于共享基境
  7. 文档中有一句话是
  8. > 在测试之间共享基境会降低测试的价值。潜在的设计问题是对象之间并非松散耦合。如果解决掉潜在的设计问题并使用桩件(stub)(参见:ref:test-doubles)来编写测试,就能达成更好的结果,而不是在测试之间产生运行时依赖并错过改进设计的机会。
  9. 暂时还没有具体理解
  10. <a name="xkIWq"></a>
  11. # 3. 关于全局状态
  12. 使用了全局状态(比如全局变量、类的静态属性、单例模式之类)的代码通常比较难以测试,欲测代码和全局状态之间的耦合度较高,且其创建无法控制。此外,一个测试对全局变量的修改可能会破坏其他的测试。
  13. <a name="uGmBH"></a>
  14. ## 单例模式的测试
  15. [拓展阅读](https://testing.googleblog.com/2008/05/tott-using-dependancy-injection-to.html)
  16. <a name="tHixz"></a>
  17. ## 全局变量
  18. > PHPUnit版本6对全局变量的处理和之前版本不同,这里仅讨论6以及之后的版本的处理方式
  19. PHPUnit提供了方法,可以用来备份与还原全局变量。在PHPUnit6之前的版本中,备份还原操作时默认开启的,之后的版本则默认关闭。可以通过添加参数开启:
  20. - 命令行中添加 `--global-backup` ,或者XML配置文件中使用 `backupGlobals="true"` 激活
  21. - 命令行中添加 `--static-bakcup` ,或者XML配置文件中使用 `backupStaticAttributes="true"` ,可以将此隔离扩展至静态属性。这个方法对函数内的静态变量是无效的。
  22. - 也可以使用 `@backupGlobals enabled/disabled` 对类/测试方法进行标注,具体[点这里](https://phpunit.readthedocs.io/zh_CN/latest/annotations.html#backupglobals)
  23. - 但是该方式比较需要注意,若标注在某个测试方法上,其仅仅意味着,这个方法的执行前后,全局变量/静态属性的值不变,而不是会还原为初始声明的默认值。所以对于静态属性,在setUp()中进行显式地重置是比较推荐的
  24. <a name="eC7Un"></a>
  25. ### 测试用例
  26. ```php
  27. 分别使用两种方式运行
  28. 方式一:phpunit --globals-backup ClassTest.php
  29. 方式二:phpunit ClassTest.php
  30. <?php
  31. use PHPUnit\Framework\TestCase;
  32. $t = 1;
  33. class ClassTest extends TestCase
  34. {
  35. public function test1(){
  36. global $t;
  37. $t += 1;
  38. echo $t; //output 2
  39. }
  40. public function test2(){
  41. global $t;
  42. echo $t; //with option: 1
  43. //without option: 2
  44. }
  45. }

备份黑名单

PHPUnit提供了添加黑名单的方式,可以在开启备份选项的时候,对特定的全局变量/静态变量不进行备份(仅列出了全局变量的方式,静态属性类似):

  • 可以使用 @backupGlobals disabled 进行标注,同样需要注意上文列出来的问题
  • 也可以在类中添加 protected $backupGlobalsBlacklist = ['globalVariable']
    • 在方法中对这个变量进行设置是无效的
    • 不过在测试的时候,报了warning: PHPUnit\Framework\TestCase::$backupGlobalsBlacklist is deprecated and will be removed in PHPUnit 10. Please use PHPUnit\Framework\TestCase::$backupGlobalsExcludeList instead. 看来官方文档更新的不够及时,使用的时候可以注意一下

      注意事项

      备份还原的操作使用的是 serialize()unserialize() 。所以某些类的实例对象(比如PDO)由于无法序列化,无法进行备份

      4. 综上所述

      对于一个测试而言,能为其提供基境的主要方式有
  1. 依赖关系——通过生产者消费者关系来建立基境
  2. setUp() 以及 tearDown() 设置公共的基境
  3. 数据供给器
  4. 自行在函数内设置