一个单元的单个或多个文件用于单元测试?


20

在研究单元测试最佳实践以帮助为我的组织制定准则时,我遇到了一个问题,即分离测试装置(测试类)还是将一个类的所有测试保存在一个文件中是更好还是有用。

首先,我纯粹是指“单元测试”,它们是针对单个类,每个测试一个断言,所有依赖项都被嘲笑的白盒测试。

一个示例场景是一个类(称为Document),它具有两个方法:CheckIn和CheckOut。每种方法实现控制其行为的各种规则等。按照每次测试一个断言的规则,每种方法我将有多个测试。我可以将所有测试放在一个单独的DocumentTests类中,其名称类似于CheckInShouldThrowExceptionWhenUserIsUnauthorizedCheckOutShouldThrowExceptionWhenUserIsUnauthorized

或者,我可以有两个单独的测试类:CheckInShouldCheckOutShould。在这种情况下,我的测试名称将被缩短,但是它们会被组织起来,以便针对特定行为(方法)的所有测试都在一起。

我确定这两种方法都有优缺点,并且想知道是否有人使用多个文件进行了路由,如果是,为什么?或者,如果您选择了单文件方法,为什么感觉更好呢?


2
您的测试方法名称太长。简化它们,即使这意味着测试方法将包含多个断言。
伯纳德

12
@Bernard:每个方法有多个断言被认为是不好的作法,但是长测试方法名称不被认为是不好的作法。我们通常会在名称本身中记录该方法正在测试的内容。例如,constructor_nullSomeList(),setUserName_UserNameIsNotValid()等...
c_maker 2011年

4
@伯纳德:我也使用长测试名(并且只有那里!)。我喜欢它们,因为很明显什么都不起作用(如果您的名字是不错的选择;)。
塞巴斯蒂安·鲍尔

如果多个断言都测试相同的结果,则它不是一个坏习惯。例如。你不会有testResponseContainsSuccessTrue()testResponseContainsMyData()testResponseStatusCodeIsOk()。您将有他们在一个testResponse()有三个断言:assertEquals(200, response.status)assertEquals({"data": "mydata"}, response.data)assertEquals(true, response.success)
尤哈Untinen

Answers:


18

这种情况很少见,但是有时候对于给定的被测类来说,有多个测试类是有意义的。通常,当需要不同的设置并在部分测试中共享时,我会这样做。


3
一个很好的例子,为什么首先向我介绍了这种方法。例如,当我想测试CheckIn方法时,我总是希望以一种方式设置被测对象,但是当我测试CheckOut方法时,则需要进行其他设置。好点子。
2011年

14

真的看不到任何令人信服的理由,为什么要将单个类的测试拆分为多个测试类。由于驱动思想应该是在班级上保持凝聚力,因此您也应该在测试级上争取它。只是一些随机原因:

  1. 无需复制(并维护多个版本)设置代码
  2. 如果它们属于一个测试类,则可以更轻松地从IDE运行该类的所有测试
  3. 通过测试类和类之间的一对一映射,更容易进行故障排除。

好点。如果要测试的类太糟糕了,我将不排除几个测试类,其中一些包含辅助逻辑。我只是不喜欢非常严格的规则。除此之外,优点。
工作

1
通过为测试夹具提供一个通用基类来消除单个设置类,我将消除重复的设置代码。我不确定我完全同意其他两点。
SonOfPirate 2011年

例如,在JUnit中,您可能想使用不同的Runner来测试事物,这导致需要不同的类。
Joachim Nilsson

当我编写一个类时,每个类中都有大量带有复杂数学方法的方法时,我发现我有85个测试,并且到目前为止,仅针对24%的方法编写了测试。我发现自己在这里,是因为我想知道它是否疯狂地以某种方式将大量测试分门别类,以便我可以运行子集。
Mooing Duck's

7

如果您不得不将一个类的单元测试拆分为多个文件,则可能表明该类本身设计不良。我想不出将合理地遵循“单一职责原则”和其他编程最佳实践的类的单元测试拆分开来会更有利的情况。

此外,在单元测试中使用更长的方法名称是可以接受的,但是如果这困扰您,您总是可以重新考虑单元测试的命名约定以缩短名称。


las,在现实世界中,并非所有类都遵循SRP等标准……而这在对遗留代码进行单元测试时就成为一个问题。
彼得Török

3
不确定SRP在这里如何关联。我的课堂上有多种方法,我需要针对驱动其行为的所有不同要求编写单元测试。最好有一个具有50种测试方法的测试类,或者有5种具有10个测试的测试类,每个测试类与被测类中的特定方法相关?
2011年

2
@SonOfPirate:如果您需要进行大量测试,则可能意味着您的方法做得太多。因此,SRP在这里非常重要。如果将SRP应用于类和方法,则将有许多易于测试的小类和方法,并且不需要太多的测试方法。(但我同意PéterTörök的观点,当您测试遗留代码时,良好的实践就在眼前,您只需要尽最大努力就可以得到所给的...)
c_maker 2011年

我可以举的最好的例子是CheckIn方法,在该方法中,我们有业务规则来确定允许谁执行操作(授权),对象是否处于待检入的正确状态(例如是否已检出以及是否有更改)已制成。我们将进行单元测试,以验证是否执行了授权规则,以及进行测试,以验证在未检出,未更改对象等情况下调用CheckIn时是否调用了该方法。测试。您是否暗示这是错误的?
2011年

我认为在这个问题上没有一成不变的,正确或错误的答案。如果您正在做的事情对您有用,那就太好了。这只是我对一般准则的看法。
FishBasketGordo 2011年

2

我反对将测试分为多个类的观点之一是,团队中的其他开发人员(尤其是那些不懂测试的开发人员)很难找到现有的测试(Gee,我想知道是否已经为此进行了测试)方法?我想知道它在哪里?),以及在何处放置新测试(我将在此类中为此方法编写测试,但不确定是否应该将它们放置在新文件或现有文件中)一个?

在不同的测试需要完全不同的设置的情况下,我已经看到一些TDD的从业者将“夹具”或“设置”放在不同的类/文件中,而不是测试本身。


1

我希望我能记得/找到我最初选择采用的技术的链接。本质上,我为每个被测类创建一个抽象类,其中包含每个被测成员的嵌套测试装置(类)。这样可以提供最初所需的分隔,但是将每个位置的所有测试都保存在同一文件中。此外,这会生成一个类名,该类名允许在测试运行器中轻松进行分组和排序。

这是此方法如何应用于我的原始方案的示例:

public abstract class DocumentTests : TestBase
{
    [TestClass()]
    public sealed class CheckInShould : DocumentTests
    {
        [TestMethod()]
        public void ThrowExceptionWhenUserIsNotAuthorized()
        {
        }
    }

    [TestClass()]
    public sealed class CheckOutShould : DocumentTests
    {
        [TestMethod()]
        public void ThrowExceptionWhenUserIsNotAuthorized()
        {
        }
    }
}

这将导致以下测试出现在测试列表中:

DocumentTests+CheckInShould.ThrowExceptionWhenUserIsNotAuthorized
DocumentTests+CheckOutShould.ThrowExceptionWhenUserIsNotAuthorized

随着更多测试的添加,它们很容易按类名称进行分组和排序,这也将所有被测类的测试列在了一起。

我已学会利用这种方法的另一个优势是这种结构的面向对象特性,这意味着我可以在DocumentTests基类的启动和清除方法中定义代码,这些代码将由所有嵌套类以及每个嵌套类共享它包含的测试的嵌套类。


1

尝试以另一种方式思考一分钟。

为什么每个班级只有一个测试班级?您要进行类测试还是单元测试?你甚至依赖上课吗?

单元测试应该在特定的上下文中测试特定的行为。您的班级已经有了一个事实,这CheckIn意味着首先应该有一种需要它的行为。

您如何看待这个伪代码:

// check_in_test.file

class CheckInTest extends TestCase {
        /** @test */
        public function unauthorized_users_cannot_check_in() {
                $this->expectException();

                $document = new Document($unauthorizedUser);

                $document->checkIn();
        }
}

现在,您不直接测试该checkIn方法。相反,您正在测试一种行为(幸运的是要检入;),并且,如果您需要将其重构Document并将其拆分为不同的类,或将另一个类合并到其中,则您的测试文件仍然是一致的,因为重构时,您永远不会更改逻辑,而只需更改结构。

用于一个类的一个测试文件只会使您在需要时难以进行重构,并且就代码本身的领域/逻辑而言,测试的意义也较小。


0

通常,我建议将测试考虑为测试类的行为而不是方法。也就是说,您的某些测试可能需要在类上调用这两种方法,以测试某些预期的行为。

通常,我从每个生产类开始一个单元测试类,但是最终可能会根据他们测试的行为将该单元测试类分为几个测试类。换句话说,我建议不要将测试类分为CheckInShould和CheckOutShould,而应根据被测单元的行为进行拆分。


0

1.考虑可能出问题的地方

在最初的开发过程中,您非常了解自己在做什么,并且两种解决方案都可以正常工作。

当测试在更改之后很久之后失败时,它将变得更加有趣。有两种可能性:

  1. 您已严重损坏CheckIn(或CheckOut)。同样,在这种情况下,单文件解决方案和双文件解决方案都可以。
  2. 您已经修改了CheckIn和和CheckOut(可能还有它们的测试),这对他们每个人都有意义,但对他们俩却没有意义。您破坏了这对货币的一致性。在这种情况下,将测试分为两个文件将使您更难理解问题。

2.考虑使用什么测试

测试有两个主要目的:

  1. 自动检查程序仍然可以正常工作。为此,测试是在一个文件中还是在多个文件中都无关紧要。
  2. 帮助人类读者理解该程序。如果将密切相关的功能的测试放在一起,这将容易得多,因为看到它们的一致性是一条快速的理解之路。

所以?

这两种观点都表明,将测试保持在一起可以有所帮助,但不会造成伤害。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.