PHPUnit最佳实践:组织测试的方法

77

我打算从头开始编写项目的phpunit测试。因此,我查看了一些项目(如Zend)以了解它们是如何做事和组织他们的测试的。

大多数都很清楚,唯一让我有些困惑的是如何正确地组织测试套件。 Zend有一个AllTests.php,从中加载其他测试套件。
尽管在类中使用了PHPUnit_Framework_TestSuite创建了一个suite对象,然后将其他套件添加到其中,但如果我查看PHPUnit版本3.4之后组织测试的文档,只有描述XML或FileHierarchy的说明。使用类来组织测试的方法已被删除。
我没有找到任何这种方法被弃用的信息,像Zend这样的项目仍在使用。

但如果它已被弃用,我如何才能使用相同的结构在xml配置中组织测试呢?如果我只想执行少量的测试,该怎么组织测试(在xml中)?也许创建几个xml,仅指定要运行的一些测试/测试套件?

因此,如果我只想测试应用程序的module1和module2,那么我是否需要为每个模块单独创建一个XML,并在其中仅为这些模块定义测试套件(由模块使用的类)。还有一个用于定义所有测试套件的XML吗?

或者,使用@group注释标记特定测试是针对module1还是module2会更好吗?

提前感谢您指向一些最佳实践。

2个回答

121
首先,我会链接到手册,然后详细介绍我在实地工作中所见所闻。 组织phpunit测试套件

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

我推荐的方法是将文件系统与xml配置相结合。
tests/
 \ unit/
 | - module1
 | - module2
 - integration/
 - functional/

使用一个简单的phpunit.xml
<testsuites>
  <testsuite name="My whole project">
    <directory>tests</directory>
  </testsuite>
</testsuites>

如果你想的话,你可以分割测试套件,但这是一个项目选择。

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

"unit"文件夹的组织

这里最常见的方法是在tests/unit/文件夹结构中反映出你的source/目录结构。

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

文件组织

  • 每个文件一个类。

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

  • 不要有测试命名空间

这只会使编写测试更冗长,因为你需要额外的use语句,所以我认为测试类应该与生产类在同一个命名空间中,但这并不是PHPUnit强制要求你做的。我发现这样做更容易且没有任何缺点。

只执行几个测试

例如,phpunit --filter Factory 执行所有 FactoryTests,而 phpunit tests/unit/logger/ 执行与日志相关的所有内容。
您可以使用 @group 标签来标记问题编号、故事或其他内容,但对于“模块”,我会使用文件夹布局。

多个 XML 文件

如果您想要拥有以下内容,创建多个 XML 文件可能会很有用:
  • 一个没有代码覆盖率的文件
  • 一个仅用于单元测试(而不是功能测试、集成测试或长时间运行的测试)的文件
  • 其他常见的“过滤”情况
  • 例如,PHPBB3 就是这样做的 他们的 phpunit.xmls

为您的测试添加代码覆盖率

由于这与启动一个新项目并进行测试相关:
  • 我的建议是使用@covers标签,就像我在博客中描述的那样(仅适用于单元测试,始终覆盖所有非公共函数,始终使用covers标签)。
  • 不要为集成测试生成覆盖率报告,这会给你一种虚假的安全感。
  • 始终使用白名单来包含所有生产代码,这样数据才不会欺骗你!

自动加载和启动测试

你的测试不需要任何形式的自动加载。PHPUnit会处理这个问题。

使用<phpunit bootstrap="file">属性来指定你的测试引导文件。将其放在tests/bootstrap.php是一个不错的选择。在那里,你可以设置应用程序的自动加载器等(或者调用应用程序的引导文件)。

总结

  • 几乎所有的东西都要使用xml配置
  • 分离单元测试和集成测试
  • 你的单元测试文件夹应该与应用程序的文件夹结构相对应
  • 要只执行特定的测试,请使用phpunit --filterphpunit tests/unit/module1
  • 从一开始就使用strict模式,并且永远不要关闭它。

可以参考的示例项目


@TheCandyMan 不客气;如果你在使用php遇到问题,可以随时加入SO或freenode #phpunit的聊天室。 - edorian
1
更新的博客文章链接: http://web.archive.org/web/20130402142841/http://edorian.posterous.com/an-introduction-to-phpunits-covers-annotation - cmt
@cmt 感谢您的更新。我已经在帖子中更新了链接,指向我的新博客位置:http://edorian.github.io/2011-11-04-An-introduction-to-phpunits-covers-annotation/ - 干杯! - edorian
@edorian 我应该把phpunit.xml文件放在哪里,以及在哪里调用xml文件? - Kumar Shanmugam

6

基本目录结构:

我一直在尝试将测试代码放在与被测试代码相同的目录中,只是文件名略有不同。迄今为止,我很喜欢这种方法。这样做的想法是你不必花费时间和精力在你的代码和测试代码之间保持目录结构同步。因此,如果您更改了代码所在的目录名称,您不需要再去查找并更改测试代码的目录名称。这也使您花费更少的时间寻找与某些代码配合使用的测试代码,因为它就在旁边。这甚至使创建带有测试代码的文件变得更加容易,因为您不必首先找到具有测试的目录,可能要创建一个新目录以匹配您正在为其创建测试的目录,然后创建测试文件。您只需在那里创建测试文件。

其中一个巨大的优点是,这意味着其他员工(不包括您,因为您永远不会这样做)更不可能因为太麻烦而避免编写测试代码。即使他们向现有类添加方法,由于可以轻松找到测试代码,他们也不太可能不想向现有测试代码中添加测试。

另一个缺点是,这使得在没有测试代码的情况下发布生产代码更加困难。尽管如果您使用严格的命名约定,仍然可能是可能的。例如,我一直在使用ClassName.php,ClassNameUnitTest.php和ClassNameIntegrationTest.php。当我想运行所有单元测试时,有一个套件会查找以UnitTest.php结尾的文件。集成测试套件类似工作。如果我愿意,我可以使用类似的技术来防止测试被发布到生产中。

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

每个类一个测试类:

对于大多数程序员来说,这已经不是实验性的内容了,但对我来说却是。我正在尝试每个被测试类只有一个测试类的方法。过去,我为每个被测试类都创建了一个完整的目录,然后在该目录中包含了几个类。每个测试类都会以某种方式设置被测试类,并且会有许多方法,每个方法都具有不同的断言。但是我开始注意到,我将这些对象放入的某些条件与其他测试类得到的条件有共同之处。重复代码变得难以处理,因此我开始创建抽象来消除它。测试代码变得非常难以理解和维护。我意识到了这一点,但是我找不到一种对我有意义的替代方案。仅仅让每个类只有一个测试类似乎无法测试足够的情况而不会使所有的测试代码都集中在一个测试类内部。现在,我对此有了不同的看法。即使我是正确的,这也会大大阻碍其他程序员和我自己编写和维护测试。现在,我正在尝试强迫自己为每个被测试类只使用一个测试类。如果我在该测试类中遇到了太多要测试的内容,我会将其视为被测试类正在做太多的事情,并应该将其拆分为多个类。为了消除重复,我尽可能坚持使用更简单的抽象,使所有内容都可以存在于一个可读的测试类中。
更新 我仍在使用并喜欢这种方法,但我发现了一种非常好的技术,可以减少测试代码的数量和重复性。重要的是,在测试类本身内编写可重用的断言方法,这些方法会被该类中的测试方法大量使用。如果将它们视为内部DSL(某种Uncle Bob提倡的DSL),则有助于我想出正确类型的断言方法。有时,您甚至可以通过接受具有简单值的字符串参数(引用要执行的测试类型)将此DSL概念推得更远(实际上创建一个DSL)。例如,有一次我创建了一种可重用的断言方法,该方法接受$left、$comparesAs和$right参数。这使得测试变得非常短小且易读,因为代码看起来像$this->assertCmp('a','<','b')。
老实说,我无法强调这一点,这是使编写测试成为可持续的基础(您和其他程序员希望继续进行的工作)。这使得测试所添加的价值能够超过它们所花费的代价。关键不是您需要使用确切的技术,而是需要使用某种可重用的抽象,以使您能够编写短小且易读的测试。这可能看起来像我正在偏离问题,但实际上不是。如果您不这样做,最终您将陷入需要为每个被测试类创建多个测试类的陷阱,并且事情真的会变得一团糟。

到目前为止,这对我工作良好。我已经能够保持我的测试类和被测试的类足够简单,以至于每个被测试的类只需要一个测试类。将测试类与其他类放在一起也很有效。现在我正在尝试将这些测试类命名为“Test.php”,而不是“UnitTest.php”和“*IntegrationTest.php”。我不再区分单元测试和集成测试,而是统称为测试。当避免使用模拟时,它们更像是集成测试。即使实际上使用数据库,这些测试也始终保持运行速度非常快。 - still_dreaming_1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接