40

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

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

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

    \SomeFramework\Utilities\AwesomeClassTest
    
    • 优点
      • 与编写 PHPUnit 测试的传统方法一致。
    • 缺点
      • 灵活性较低。
      • 似乎打破了使用命名空间背后的原则——不相关的测试被分组到同一个命名空间中。

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

    \SomeFramework\Utilities\AwesomeClass\Test
    
    • 优点
      • 提供一种非常简单/明显的方式来将多个相关的 TestCase 类组合在一起,例如针对不同的测试套件。
    • 缺点
      • 可能会导致更深、更复杂的层次结构。
4

3 回答 3

41

我提出的解决方案及其背后的原因:

文件夹布局:

.
├── src
│   ├── bar
│   │   └── BarAwesomeClass.php
│   └── foo
│       └── FooAwesomeClass.php
└── tests
    ├── helpers
    │   └── ProjectBaseTestClassWithHelperMethods.php
    ├── integration
    │   ├── BarModuleTest.php
    │   └── FooModuleTest.php
    └── unit
        ├── bar
        │   └── BarAwesomeClassTest.php
        └── foo
            └── FooAwesomeClassTest.php

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有些人不希望这样。对于外部使用,或者在运送您的源代码时,这当然不是问题,因为测试没有运送,但可能需要考虑。

于 2012-08-27T12:02:11.687 回答
20

我使用了第三个选项,它非常适合作曲家自动加载:Test在层次结构的第一步之后插入命名空间。在你的情况下,命名空间是\SomeFramework\Tests\Utilities\,你的类是\SomeFramework\Tests\Utilities\AwesomeClassTest.

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

{
    "autoload": {
        "psr-0": { 
            "SomeFramework\\": "src/",
        }
    },
    "autoload-dev": {
        "psr-0": { 
            "SomeFramework\\Tests\\": "tests/"
        }
    }
}

第三种方法的优点是:

  • 测试和生产代码分离
  • 测试和生产类的类似文件夹层次结构
  • 轻松自动加载
于 2014-01-07T09:55:37.437 回答
4

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

每隔一段时间,测试用例就需要支持文件,例如固定装置,但这些文件很容易组织到为该类命名的子目录/命名空间中,并且通常在多个测试用例之间共享。

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

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