在命名空间中组织PHPUnit测试

Wil*_*lco 34 php phpunit unit-testing

我看到了将PHPUnit单元测试组织到命名空间层次结构中的两种选择.这两种方法有哪些优点/缺点?是否有任何明显的缺陷我没有考虑过会使一个明显更好的选择?

考虑一个示例类,如\SomeFramework\Utilities\AwesomeClass:

  • 方法1:将每个TestCase类放入与覆盖类相同的名称空间中.

    \SomeFramework\Utilities\AwesomeClassTest
    
    Run Code Online (Sandbox Code Playgroud)
    • 好处
      • 与编写PHPUnit测试的传统方法一致.
    • 缺点
      • 灵活性较差.
      • 似乎打破了使用命名空间的原则 - 不相关的测试被分组到同一个命名空间中.

  • 方法2:将每个TestCase放在以覆盖类命名的命名空间中.

    \SomeFramework\Utilities\AwesomeClass\Test
    
    Run Code Online (Sandbox Code Playgroud)
    • 好处
      • 提供了一种非常简单/明显的方法来将多个相关的TestCase类组合在一起,例如针对不同的测试套件.
    • 缺点
      • 可能会导致更深层次,更复杂的层次结构.

edo*_*ian 33

我提出的解决方案及其背后的推理:

文件夹布局:

.
??? src
?   ??? bar
?   ?   ??? BarAwesomeClass.php
?   ??? foo
?       ??? FooAwesomeClass.php
??? tests
    ??? helpers
    ?   ??? ProjectBaseTestClassWithHelperMethods.php
    ??? integration
    ?   ??? BarModuleTest.php
    ?   ??? FooModuleTest.php
    ??? unit
        ??? bar
        ?   ??? BarAwesomeClassTest.php
        ??? foo
            ??? FooAwesomeClassTest.php
Run Code Online (Sandbox Code Playgroud)

helpers/文件夹包含不是测试但仅用于测试上下文的类.通常该文件夹包含一个BaseTestClass,可能包含项目特定的帮助器方法和一些易于重用的存根类,因此您不需要那么多的模拟.

integration/文件夹包含跨越更多类的测试,并测试系统的"更大"部分.您没有那么多,但没有1:1映射到生产类.

unit/文件夹将1:1映射到src/.因此,对于每个生产类,都有一个类包含该类的所有单元测试.

命名空间

方法1:将每个TestCase类放入与覆盖类相同的名称空间中.

此文件夹方法应解决方法1的一个缺点.你仍然可以灵活地进行更多的测试,而不是纯粹的1:1映射可以给你,但一切都是有序和适当的.

似乎打破了使用命名空间的原则 - 不相关的测试被分组到同一个命名空间中.

如果测试感觉"无关",那么生产代码可能会出现同样的问题吗?

确实,测试不依赖于彼此,但他们可能会使用"关闭"类作为模拟,或者在DTO或值对象的情况下使用真实类.所以我说有一个联系.

方法2:将每个TestCase放在以覆盖类命名的命名空间中.

有几个项目可以做到这一点,但通常它们的结构有点不同:

它不是\SomeFramework\Utilities\AwesomeClass\Test,但\SomeFramework\Tests\Utilities\AwesomeClassTest它们仍保持1:1映射,但添加了额外的测试命名空间.

额外测试命名空间

我个人认为,我不喜欢有单独的测试名称空间,我会尝试找到一对支持和反对该选择的参数:

测试应作为如何使用类的文档

当真正的类在另一个命名空间中时,测试显示如何在其自己的模块之外使用该类.

当真实类位于同一名称空间中时,测试将显示如何在该模块内使用该类.

差异非常小(通常是几个"使用"语句或完全限定的路径)

当我们有可能$this->getMock(AwesomeClass::CLASS)在PHP 5.5中说而不是$this->getMock('\SomeFramework\Utilities\AwesomeClass')每个mock都需要一个use语句.

对我来说,模块中的用法对大多数课程来说更有价值

污染"生产"命名空间

当你说new \SomeFramework\Utilities\A的自动完成功能可能会显示你AwesomeClassAwesomeClassTest一些人不希望出现这种情况.对于外部使用,或者在运输您的源时当然不是问题,因为测试没有发货,但可能需要考虑.


chi*_*org 15

我使用的第三个选项与编辑器自动加载非常吻合:Test在层次结构的第一步之后插入命名空间.在你的情况下,名称空间将是\SomeFramework\Tests\Utilities\你的类\SomeFramework\Tests\Utilities\AwesomeClassTest.

然后,您可以将测试与其他类一起放在\SomeFramework\Test目录中,也可以将它们放在一个单独的目录中.您的自动加载信息composer.json可能如下所示:

{
    "autoload": {
        "psr-0": { 
            "SomeFramework\\": "src/",
        }
    },
    "autoload-dev": {
        "psr-0": { 
            "SomeFramework\\Tests\\": "tests/"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

第三种方法的优点是:

  • 分离测试和生产代码
  • 测试和生产类的类似文件夹层次结构
  • 轻松自动加载

  • @sanmai使用不必要的文件,自动加载文件会在生产中变大吗? (3认同)

Dav*_*ess 4

我更喜欢第一种方法来保持一致性——与 PHPUnit 实践和我们的其他项目。此外,我为每个被测类仅创建一个测试用例。将每个都放在自己的命名空间中似乎有些过分了。正如 KingCrunch 所说,测试是相关的,因为它们测试的类是相关的。

测试用例经常需要支持文件(例如固定装置),但这些文件很容易组织到以类命名的子目录/命名空间中,并且通常在多个测试用例之间共享。

第二种方法的一大缺点是每个测试用例的名称Test都会产生几个后果:

  • 多个打开的测试窗口都将具有相同的名称。
  • 您的 IDE 的“按名称打开类型”功能(NetBeans 中的 CTRL-O)对于测试将毫无用处。
  • IDE 的“转到测试”快捷键(NetBeans 中的 CTRL-SHIFT-T)也可能会失败。