php PHPUnit 组织测试的最佳实践

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8313283/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-26 04:24:51  来源:igfitidea点击:

PHPUnit best practices to organize tests

phpphpunit

提问by enricog

I am currently going to start from scratch with the phpunit tests for a project. So I was looking into some projects (like Zend) to see how they are doing things and how they organizing their tests.

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

Most things are pretty clear, only thing I have some problems with is how to organize the test suites properly. Zend has an AllTests.php from which loads others test suites.
Tough looking at the class it is useing PHPUnit_Framework_TestSuiteto create a suite object and then add the other suites to it, but if I look in the PHPUnit docs for organizing tests in PHPUnit versions after 3.4 there is only a description for XML or FileHierarchy. The one using classes to organize the tests was removed.
I haven't found anything that this method is deprecated and projects like Zend are still using it.

大多数事情都非常清楚,我唯一遇到的问题是如何正确组织测试套件。Zend 有一个 AllTests.php,从中加载其他测试套件。
仔细查看它PHPUnit_Framework_TestSuite用于创建套件对象然后向其中添加其他套件的类,但是如果我查看 PHPUnit 文档以在 3.4 之后的 PHPUnit 版本中组织测试,则只有 XML 或 FileHierarchy 的描述。使用类来组织测试的那个被删除了。
我没有发现这种方法已被弃用,而且像 Zend 这样的项目仍在使用它。

But if it is deprecated, how would I be able to organize tests in the same structure with the xml configuration? Executing all tests is no problem, but how would I organize the tests (in the xml) if I only wanted to execute a few tests. Maybe creating several xmls where I only specify a few tests/test suites to be run?

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

So if I would want to only test module1 and module2 of the application, would I have an extra xml for each and defining test suites only for those modules (classes used by the module) in it. And also one that defines a test suite for all tests?

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

Or would it be better to use the @groupannotation on the specific tests to mark them to be for module1 or module2?

或者最好@group在特定测试上使用注释来标记它们是用于模块 1 还是模块 2?

Thanks in advance for pointing me to some best practices.

预先感谢您向我指出一些最佳实践。

回答by edorian

I'll start of by linking to the manual and then going into what I've seen and heard in the field.

我将首先链接到手册,然后进入我在该领域所见所闻。

Organizing phpunit test suites

组织 phpunit 测试套件

Module / Test folder organization in the file system

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

My recommended approach is combining the file system with an xml config.

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

tests/
 \ unit/
 | - module1
 | - module2
 - integration/
 - functional/

with a phpunit.xmlwith a simple:

phpunit.xml一个简单的:

<testsuites>
  <testsuite name="My whole project">
    <directory>tests</directory>
  </testsuite>
</testsuites>

you can split the testsuites if you want to but thats a project to project choice.

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

Running phpunitwill then execute ALL tests and running phpunit tests/unit/module1will run all tests of module1.

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

Organization of the "unit" folder

“单元”文件夹的组织

The most common approach here is to mirror your source/directory structure in your tests/unit/folder structure.

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

You have one TestClass per ProductionClass anyways so it's a good approach in my book.

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

In file organization

在文件组织中

  • One class per file.
  • 每个文件一个类。

It's not going to work anyways if you have more than one test class in one file so avoid that pitfall.

如果一个文件中有多个测试类,它无论如何都不会起作用,因此要避免这种陷阱。

  • Don't have a test namespace
  • 没有测试命名空间

It just makes writing the test more verbose as you need an additional use statement so I'd say the testClass should go in the same namespace as the production class but that is nothing PHPUnit forces you to do. I've just found it to be easier with no drawbacks.

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

Executing only a few tests

只执行几个测试

For example phpunit --filter Factoryexecutes all FactoryTests while phpunit tests/unit/logger/executes everything logging related.

例如,phpunit --filter Factoryphpunit tests/unit/logger/执行与日志相关的所有内容的同时执行所有 FactoryTest 。

You can use @grouptags for something like issue numbers, stories or something but for "modules" I'd use the folder layout.

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

Multiple xml files

多个xml文件

It can be useful to create multiple xml files if you want to have:

如果您想拥有以下内容,创建多个 xml 文件会很有用:

  • one without code coverage
  • one just for the unit tests (but not for the functional or integration or long running tests)
  • other common "filter" cases
  • PHPBB3 for example does that for their phpunit.xmls
  • 一个没有代码覆盖
  • 一个仅用于单元测试(但不适用于功能或集成或长时间运行的测试)
  • 其他常见的“过滤器”案例
  • 例如 PHPBB3 就是这样做的 their phpunit.xmls

Code coverage for your tests

测试的代码覆盖率

As it is related to starting a new project with tests:

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

  • My suggestion is to use @coverstags like described in my blog(Only for unit tests, always cover all non public functions, always use covers tags.
  • Don't generate coverage for your integration tests. It gives you a false sense of security.
  • Always use whitelisting to include all of your production code so the numbers don't lie to you!
  • 我的建议是使用我博客中描述的@covers标签(仅用于单元测试,始终覆盖所有非公共功能,始终使用覆盖标签。
  • 不要为您的集成测试生成覆盖率。它会给你一种虚假的安全感。
  • 始终使用白名单来包含您的所有生产代码,这样数字就不会骗您!

Autoloading and bootstrapping your tests

自动加载和引导您的测试

You don't need any sort of auto loading for your tests. PHPUnit will take care of that.

您的测试不需要任何类型的自动加载。PHPUnit 会处理这个问题。

Use the <phpunit bootstrap="file">attribute to specify your test bootstrap. tests/bootstrap.phpis a nice place to put it. There you can set up your applications autoloader and so on (or call your applications bootstrap for that matter).

使用该<phpunit bootstrap="file">属性来指定您的测试引导程序。tests/bootstrap.php是一个放置它的好地方。在那里你可以设置你的应用程序自动加载器等等(或者为此调用你的应用程序引导程序)。

Summary

概括

  • Use the xml configuration for pretty much everything
  • Seperate unit and integration tests
  • Your unit test folders should mirror your applications folder structure
  • To only execute specif tests use phpunit --filteror phpunit tests/unit/module1
  • Use the strictmode from the get go and never turn it off.
  • 几乎所有内容都使用 xml 配置
  • 分离单元测试和集成测试
  • 您的单元测试文件夹应该反映您的应用程序文件夹结构
  • 要仅执行特定测试,请使用phpunit --filterphpunit tests/unit/module1
  • strict从一开始就使用该模式,永远不要关闭它。

Sample projects to look at

要查看的示例项目

回答by still_dreaming_1

Basic Directory Structure:

基本目录结构

I have been experimenting with keeping the test code right next to the code being tested, literally in the same directory with a slightly different file name from the file with the code it is testing. So far I am liking this approach. The idea is you don't have to spend time and energy keeping the directory structure in sync between your code and your test code. So if you change the name of the directory the code is in, you don't then also need to go and find and change the directory name for the test code. This also causes you to spend less time looking for the test code that goes with some code as it is right there next to it. This even makes it less of a hassle to create the file with the test code to begin with because you don't have to first find the directory with the tests, possibly create a new directory to match the one you are creating tests for, and then create the test file. You just create the test file right there.

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

One huge advantage of this is it means the other employees (not you because you would never do this) will be less likely to avoid writing test code to begin with because it is just too much work. Even as they add methods to existing classes they will be less likely to not feel like adding tests to the existing test code, because of the low friction of finding the test code.

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

One disadvantage is this makes it harder to release your production code without the tests accompanying it. Although if you use strict naming conventions it still might be possible. For example, I have been using ClassName.php, ClassNameUnitTest.php, and ClassNameIntegrationTest.php. When I want to run all the unit tests, there is a suite that looks for files ending in UnitTest.php. The integration test suite works similarly. If I wanted to, I could use a similar technique to prevent the tests from getting released to production.

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

Another disadvantage of this approach is when you are just looking for actual code, not test code, it takes a little more effort to differentiate between the two. But I feel this is actually a good thing as it forces us to feel the pain of the reality that test code is code too, it adds its' own maintenance costs, and is just as vitally a part of the code as anything else, not just something off to the side somewhere.

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

One test class per class:

每节课一个测试课:

This is far from experimental for most programmers, but it is for me. I am experimenting with only having one test class per class being tested. In the past I had an entire directory for each class being tested and then I had several classes inside that directory. Each test class setup the class being tested in a certain way, and then had a bunch of methods each one with a different assertion made. But then I started noticing certain conditions I would get these objects into had stuff in common with other conditions it got into from other test classes. The duplication become too much to handle, so I started creating abstractions to remove it. The test code became very difficult to understand and maintain. I realized this, but I couldn't see an alternative that made sense to me. Just having one test class per class seemed like it would not be able to test nearly enough situations without becoming overwhelming to have all that test code inside one test class. Now I have a different perspective on it. Even if I was right, this is a huge dampener on other programmers, and myself, wanting to write and maintain the tests. Now I am experimenting with forcing myself to have one test class per class being tested. If I run into too many things to test in that one test class, I am experimenting with seeing this as an indication that the class being tested is doing too much, and should be broken up into multiple classes. For removing duplication I am trying to stick to simpler abstractions as much as possible that allows everything to exist in one readable test class.

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

UPDATEI am still using and liking this approach, but I have found a very good technique for reducing the amount of test code and the amount of duplication. It is important to write reusable assertion methods inside the test class itself that gets heavily used by the test methods in that class. It helps me to come up with the right types of assertion methods if I think of them as internal DSLs (something Uncle Bob promotes, well actually he promotes actually making internal DSLs). Sometimes you can take this DSL concept even further (actually make a DSL) by accepting a string parameter that has a simple value that refers to what kind of test you are trying to perform. For example, one time I made a reusable asssertion method that accepted a $left, $comparesAs, and a $right parameter. This made the tests very short and readable as the code read something like $this->assertCmp('a', '<', 'b').

更新我仍然在使用并喜欢这种方法,但我发现了一种非常好的技术来减少测试代码的数量和重复的数量。在测试类本身中编写可重用的断言方法很重要,该方法被该类中的测试方法大量使用。如果我将它们视为内部 DSL(鲍勃叔叔提倡的东西,实际上他提倡实际制作内部 DSL),这有助于我提出正确类型的断言方法。有时您可以通过接受一个字符串参数来进一步理解这个 DSL 概念(实际上是创建一个 DSL),该参数具有一个简单的值,该值指代您尝试执行的测试类型。例如,有一次我创建了一个可重用的断言方法,它接受一个 $left、$comparesAs 和一个 $right 参数。$this->assertCmp('a', '<', 'b').

Honestly, I can't emphasize that point enough, it is the entire foundation of making writing tests something that is sustainable (that you and the other programmers want to keep doing). It makes it possible for the value that tests add to more than what they take away. The point is not that you need to use that exact technique, the point is you need to use some kind of reusable abstractions that allow you to write short and readable tests. It might seem like I'm getting off topic from the question, but I'm really not. If you don't do this, you will eventually fall into the trap of needing to create multiple test classes per class being tested, and things really break down from there.

老实说,我怎么强调这一点都不为过,它是使编写测试具有可持续性的整个基础(您和其他程序员希望继续这样做)。它使测试增加的价值可能超过其带走的价值。关键不是你需要使用那种精确的技术,关键是你需要使用某种可重用的抽象,允许你编写简短易读的测试。看起来我似乎脱离了这个问题,但我真的不是。如果你不这样做,你最终会陷入需要为每个被测试的类创建多个测试类的陷阱,事情真的从那里开始崩溃。