【问题标题】:SUT - Testing Internal Behaviour TDDSUT - 测试内部行为 TDD
【发布时间】:2016-01-31 19:55:51
【问题描述】:

我有一个关于测试要求的问题。

举个例子:

Class Article {

    public void deactive() {//some behaviour, which deactives article}

}

我的要求是,文章可以是deactived。我将把它作为我的Article 课程的一部分来实现。

Test 将调用deactive 方法,但现在呢?我需要在测试结束时检查是否满足要求,所以我需要实现方法isDeactived
这是我的问题,如果我真的应该这样做吗?此方法将仅由我的测试用例使用,其他任何地方都不会。所以我使我的类接口复杂化,只是为了看看断言,如果真的被停用了。

实现它的正确方法是什么?

【问题讨论】:

  • 如果您对已停用的文章调用停用会怎样? something 如何知道文章可以/应该被停用?

标签: php unit-testing testing tdd bdd


【解决方案1】:

通常认为向类添加测试挂钩是可以的。最好有一个稍微杂乱的界面并知道你的类是有效的,而不是让它无法测试。但是还有一些其他的解决方案。

如果您可以将您的方法设置为受保护或包私有,您也许可以使用Guava's @VisibleForTesting annotation 之类的东西。 Java 以外的语言可能有其他类似的库。

您还可以从 Article 继承来访问私有字段。

class ArticleTest extends Article {
    @Test
    public void deactiveTest() {
        this.deactive();
        assertTrue(this.isDeactive);    
    }
}

这一切都假设您有一些用于标记对象是否处于活动状态的字段。

您可能会造成一些副作用,例如调用数据库,以及一些服务说您正在停用该文章。如果是这样,您应该模拟您用来产生副作用的协作者并验证您是否正确调用它们。

例如(在 java/mockito 之类的伪代码中):

@Test
public void deactiveTest() {
    Article underTest = new Article(databaseMock, httpMock); //or use dependency injection framework of your choice...
    underTest.deactive();
    verify(databaseMock).calledOnce().withParams(expectedParams);
    verify(httpMock).calledOnce().withParams(expectedParams);
}

最后一种可能性,如果该字段影响其他方法或函数的行为,您可以尝试(再次在伪代码中):

article.deactive()
result = article.post() // maybe returns true if active, else false?
assertFalse(result)

这样,您正在测试结果行为,而不仅仅是检查内部状态。

【讨论】:

  • 我正在使用 PHP。 Java 中没有类似受保护范围的东西。既不是允许这样做的图书馆。我将不得不参加反射课程并将私有财产公开。但是我的测试会变得更加混乱。但我想这是要付出的代价,对吧?
  • PHP 具有继承性、至少一个 DI 框架(尽管如果您使用构造函数参数则不需要一个)以及通过 php-unit 创建模拟的能力。这就是你所需要的。
  • 这里我说的不是嘲讽。我说的是被测系统(SUT)。此处测试的课程不使用任何协作者。这里的问题是关于创建公共方法来显示 SUT 的内部状态,仅用于测试目的。
  • 好吧,我猜反射类不会解决问题。它将我的测试用例与我的课程结合起来。例如,如果我更改班级中的属性名称,我将需要更改我的测试用例。
  • 听起来没有一种方法是理想的,但这表明您使用的语言存在缺陷。我建议在它们之间进行选择时考虑哪种解决方案对您未来的自己来说最易读,并且如果您最终与实现耦合,那么它不太可能或容易更改,例如字段的名称.例如您的开发环境应该能够更改使用字段的名称。
【解决方案2】:

听起来您正在编写一个类似以下内容的测试:

assertThatCallingDeactiveMarksArticleAsDeactivated

使用isDeactivated 方法,此测试变得微不足道,但是正如您所说,您的Article 类不包含此方法。所以,问题变成了应该它有那个方法。答案实际上取决于 Article 类成为 deactive 的真正含义。

我希望 active Article 在某种程度上与 deactive Article 表现不同。否则,状态变化似乎没有理由/没有什么可测试的。

举一个实际的例子,从Article类的客户的角度来看。 某事触发了对deactive 的调用。它可能就像用户单击用户界面上的停用按钮/链接一样简单,它会调用Article 类。在此调用之后,我希望用户界面以某种方式反映 Article 处于非活动状态(例如通过灰显按钮/链接),但要做到这一点,用户界面需要能够读取Article 的状态,这让我们回到了它如何做到这一点和/或为什么当前代码不需要 isDeactivated 方法的问题?

不知道更多关于 deactive 方法的实现(它是简单地设置一个标志,还是以可观察的方式调用其他代码)以及状态变化如何影响 Article 的行为以及它客户很难给出更具体的回应。

【讨论】:

  • 它只是设置了一个标志。它不调用任何其他对象,也没有任何其他调用 isActive 或 isDeactived on Article。就像您说的那样,用户调用无效。但是他如何在没有公共 get 方法的情况下读取值?通过使用 CQRS 和分离写/读。文章是写模型,它只为写操作加载,从不为读操作加载。并且读取模型使用清晰的 SQL 直接进入数据库。
  • @Dariss 如果文章正在写入数据库,那么我希望您正在调用数据库(在这种情况下应该有模拟/存根的范围),或者数据库框架正在访问类的字段,在这种情况下,您应该能够使用相同的方法从测试中访问它们。也就是说,有些人主张您不需要测试简单的 setter 功能stackoverflow.com/questions/6197370/…,如果它不太可能经常更改,那么简单的集成测试可能是要走的路......
  • 我的数据库框架是ORM,他不使用set/get方法,而是反射。但是您的集成测试建议很有帮助。但不是集成,我会在那里写功能。 :)
  • @Dariss 如果您确实想编写单元测试并且您的 ORM 使用反射来访问您的类字段而不需要公开它们,那么似乎使用反射来测试您的方法是一种方式去。您关心的一点是“调用 deactive 是否设置了 ORM 使用的字段”,而不是“调用 deactive 后调用 isDeactived 是否返回 true”。在这种情况下添加一个额外的方法,似乎只会增加您测试错误事物的范围......
【解决方案3】:

理想情况下,您不想测试方法或类的内部,因为这会使测试变得脆弱且紧密耦合。如果你重构生产代码,你的测试有更高的变化也需要重构,从而有利于测试。您想尝试测试整个类的行为(即调用 deactivate 的作用)

查看 Kent Beck 的 4 条简单设计规则(按优先顺序排列)。

1) 所有测试通过

2) 表达意图

3) 消除重复

4) 最少的元素

最后一条规则是,系统的大小不应超过所需大小,这就是您的问题所在。鉴于这是 最不重要的元素 并且它更好地 1)通过测试和 2)表达意图,(在我看来)简单地添加一个 isActive() 方法是可以接受的。

这也使类更有用和更通用,就像你停用某些东西一样,能够验证它的状态似乎是合乎逻辑的。

同样如前所述,调用 deactivate 必须有一个效果,它本身应该被测试,所以尝试测试一下 - 它可能更好地放置集成测试,或者你必须模拟或存根另一个类。

【讨论】:

    猜你喜欢
    • 2017-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多