关于php:PHPUnit组织测试的最佳实践

PHPUnit best practices to organize tests

我目前要从头开始使用phpunit测试项目。所以我正在研究一些项目(比如Zend),看看他们是如何做事的以及他们如何组织他们的测试。

大多数事情都非常清楚,只有我遇到的一些问题是如何正确组织测试套件。
Zend有一个AllTests.php,从中加载其他测试套件。
艰难地看一下它正在使用PHPUnit_Framework_TestSuite来创建一个套件对象,然后将其他套件添加到它中,但如果我查看PHPUnit文档在3.4之后的PHPUnit版本中组织测试,那么只有XML或FileHierarchy的描述。使用类来组织测试的那个被删除了。
我没有发现任何不推荐使用此方法的内容,以及像Zend这样的项目仍在使用它。

但是如果它被弃用,我怎么能用xml配置在相同的结构中组织测试?执行所有测试都没有问题,但如果我只想执行一些测试,我将如何组织测试(在xml中)。也许创建几个xmls,我只指定要运行的几个测试/测试套件?

因此,如果我只想测试应用程序的module1和module2,我是否每个都有一个额外的xml,并且仅为那些模块(模块使用的类)定义测试套件。还有一个为所有测试定义测试套件的?

或者在特定测试中使用@group注释将它们标记为module1或module2会更好吗?

提前感谢您指出一些最佳做法。


我将首先链接到手册,然后进入我在现场看到和听到的内容。

组织phpunit测试套件

文件系统中的模块/测试文件夹组织

我推荐的方法是将文件系统与xml配置相结合。

1
2
3
4
5
6
tests/
 \ unit/
 | - module1
 | - module2
 - integration/
 - functional/

使用简单的phpunit.xml

1
2
3
4
5
<testsuites>
  <testsuite name="My whole project">
    <directory>tests</directory>
  </testsuite>
</testsuites>

如果你愿意,你可以拆分测试套件,但这是项目选择的项目。

然后运行phpunit将执行所有测试,运行phpunit tests/unit/module1将运行module1的所有测试。

组织"单元"文件夹

这里最常用的方法是在tests/unit/文件夹结构中镜像source/目录结构。

无论如何,每个ProductionClass都有一个TestClass,所以这是我书中的一个好方法。

在文件组织中

  • 每个文件一个类。

如果你在一个文件中有多个测试类,那么它无论如何都不会起作用,所以要避免这个陷阱。

  • 没有测试命名空间

它只是让编写测试更加冗长,因为你需要一个额外的use语句,所以我说testClass应该与生产类位于同一个命名空间中,但这不是PHPUnit强迫你做的事情。我刚刚发现它更容易没有任何缺点。

只执行几个测试

例如,phpunit --filter Factory执行所有FactoryTests,而phpunit tests/unit/logger/执行与日志相关的所有操作。

您可以使用@group标签来查找问题编号,故事或其他内容,但对于"模块",我会使用文件夹布局。

多个xml文件

如果您想要创建多个xml文件,这将非常有用:

  • 一个没有代码覆盖
  • 一个只用于单元测试(但不适用于功能或集成或长时间运行的测试)
  • 其他常见的"过滤"案例
  • 例如,PHPBB3为their phpunit.xmls执行此操作

测试的代码覆盖率

因为它与使用测试启动新项目有关:

  • 我的建议是使用我的博客中描述的@covers标签(仅用于单元测试,始终覆盖所有非公共功能,始终使用封面标签。
  • 不要为集成测试生成覆盖范围。它给你一种虚假的安全感。
  • 始终使用白名单来包含您的所有生产代码,这样数字就不会对您产生影响!

自动加载和引导您的测试

您的测试不需要任何类型的自动加载。 PHPUnit将负责这一点。

使用属性指定测试引导程序。 tests/bootstrap.php是一个放置它的好地方。在那里,您可以设置应用程序自动加载器等(或调用您的应用程序引导程序)。

摘要

  • 几乎所有内容都使用xml配置
  • 单独的单元和集成测试
  • 单元测试文件夹应该镜像应用程序文件夹结构
  • 要仅执行特定测试,请使用phpunit --filterphpunit tests/unit/module1
  • 从开始使用strict模式,永远不要将其关闭。

要查看的示例项目

  • Sebastian Bergmanns"银行账户"示例项目
  • phpBB3即使如此,他们也必须与他们的遗产进行斗争;)
  • Symfony2的
  • Doctrine2


基本目录结构:

我一直在尝试将测试代码保持在正在测试的代码旁边,字面上在同一目录中,文件名与文件名略有不同,并且正在测试代码。到目前为止,我喜欢这种方法。这个想法是你不必花时间和精力保持代码和测试代码之间的目录结构同步。因此,如果更改代码所在目录的名称,则不需要再查找和更改测试代码的目录名称。这也使您花费更少的时间来寻找与某些代码相关的测试代码,因为它就在旁边。这甚至可以减少使用测试代码创建文件的麻烦,因为您不必首先找到包含测试的目录,可能创建一个新目录以匹配您正在为其创建测试的目录,以及然后创建测试文件。您只需在那里创建测试文件。

这方面的一个巨大优势是它意味着其他员工(不是因为你永远不会这样做)将不太可能避免编写测试代码,因为它只是太多的工作。即使他们将方法添加到现有类中,他们也不太可能不想在现有测试代码中添加测试,因为找到测试代码的摩擦很小。

一个缺点是,如果没有附带的测试,就很难发布生产代码。虽然如果使用严格的命名约定,它仍然可能。例如,我一直在使用ClassName.php,ClassNameUnitTest.php和ClassNameIntegrationTest.php。当我想运行所有单元测试时,有一个套件可以查找以UnitTest.php结尾的文件。集成测试套件的工作方式类似。如果我想,我可以使用类似的技术来防止测试被发布到生产中。

这种方法的另一个缺点是,当您只是在寻找实际代码而不是测试代码时,需要花费更多精力来区分这两者。但我觉得这实际上是一件好事,因为它迫使我们感受到测试代码也是代码的现实的痛苦,它增加了它自己的维护成本,并且与其他任何东西一样,也是代码的一部分。只是在某个地方的东西。

每个班级一个测试类:

对于大多数程序员来说,这远非实验性的,但它适合我。我正在尝试每个类只测试一个测试类。在过去,我为每个正在测试的类都有一个完整的目录,然后我在该目录中有几个类。每个测试类以某种方式设置要测试的类,然后有一堆方法,每个方法都有不同的断言。但后来我开始注意到某些条件,我会将这些对象与其他测试类中的其他条件相同。复制变得太难以处理,所以我开始创建抽象来删除它。测试代码变得非常难以理解和维护。我意识到了这一点,但我看不到对我有意义的替代方案。每个类只有一个测试类似乎无法测试几乎足够的情况而不会在一个测试类中包含所有测试代码。现在我对它有不同的看法。即使我是对的,这对其他程序员和我自己来说也是一个巨大的阻碍,他们想要编写和维护测试。现在我正在尝试强迫自己每个类测试一个测试类。如果我在一个测试类中遇到太多要测试的东西,我正在尝试将此视为一个指示,即被测试的类做得太多,应该分解为多个类。为了消除重复,我试图尽可能地坚持更简单的抽象,允许一切都存在于一个可读的测试类中。

UPDATE
我仍在使用和喜欢这种方法,但我发现了一种非常好的技术来减少测试代码的数量和重复的数量。在测试类本身中编写可重用的断言方法非常重要,这些方法会被该类中的测试方法大量使用。如果我将它们视为内部DSL(Bob叔叔推广的东西,实际上他实际上是在制作内部DSL),它可以帮助我提出正确类型的断言方法。有时你可以通过接受一个字符串参数来进一步采用这种DSL概念(实际上是DSL),该参数具有一个简单的值,指的是你试图执行什么样的测试。例如,有一次我做了一个可重用的dissertion方法,接受$ left,$ comparisonAs和$ right参数。这使得测试非常简短且可读,因为代码读取的内容类似于$this->assertCmp('a', '<', 'b')

老实说,我不能强调这一点,它是编写测试的整个基础,是可持续的(你和其他程序员想要继续做的事情)。 它使得测试所增加的价值超过他们带走的价值成为可能。 关键不在于你需要使用那种确切的技术,关键是你需要使用某种可重用的抽象,它允许你编写简短易读的测试。 看起来我似乎从问题中脱离主题,但我真的不是。 如果你不这样做,你最终将陷入需要为每个被测试的类创建多个测试类的陷阱,并且事情真的从那里崩溃了。

好。