难以进行严格设计的单元测试吗?

时间:2010-04-17 15:20:38

标签: unit-testing

我现在正在对一年中写的应用程序进行单元测试,然后才开始努力进行单元测试。我意识到我写的类很难进行单元测试,原因如下:

  1. 依赖于从数据库加载数据。这意味着我必须在表中设置一行才能运行单元测试(我没有测试数据库功能)。
  2. 需要很多其他外部类才能让我测试的类达到其初始状态。
  3. 总的来说,设计似乎没有任何问题,只是它太紧密耦合(这本身就是一件坏事)。我认为如果我已经为每个类编写了自动化测试用例,因此确保我没有堆积额外的依赖关系或耦合使该类工作,该类可能设计得更好。

    这个理由是否有水?你有什么经历?

8 个答案:

答案 0 :(得分:13)

是的,你是对的。一个不可单元测试难以进行单元测试的类(几乎总是)没有很好的设计(有例外,一如既往,但这些是罕见的 - 恕我直言,最好不要试图解释问题这条路)。缺乏单元测试意味着它难以维护 - 无论何时修改其中的任何内容,您都无法知道是否已破坏现有功能。

此外,如果它(与)共同依赖于程序的其余部分,那么它的任何变化都可能会破坏甚至在看似无关的远距离部分代码中。

TDD不仅仅是一种测试代码的方式 - 它也是一种不同的设计方式。有效地使用 - 并且考虑使用 - 从一开始就使用您自己的类和接口可能会导致与传统的“代码和祈祷”方式截然不同的设计。一个具体的结果是,通常大多数关键代码都与系统的边界隔离,即有适当的包装/适配器来隐藏,例如来自系统其余部分的具体数据库,以及“有趣”(即可测试)代码不在这些包装器中 - 这些尽可能简单 - 但在系统的其余部分。

现在,如果您有一堆没有单元测试的代码并想要覆盖它,那么您就有了挑战。模拟框架可能会有很大的帮助,但是为这样的代码编写单元测试仍然很麻烦。 Michael Feathers是Working Effectively with Legacy Code处理此类问题的一种很好的技术来源(通常称为遗留代码)。

答案 1 :(得分:11)

是的,使用更松散的耦合设计可能会更好,但最终您需要测试数据。

您应该研究Mocking框架来模拟数据库以及该类所依赖的其他类,以便您可以更好地控制测试。

答案 2 :(得分:11)

我发现dependency injection是最有助于使我的代码可测试的设计模式(并且通常也可重用并适用于与我为其设计的原始内容不同的上下文)。我的Java同事崇拜Guice;我主要用Python编程,所以我通常会“手动”进行依赖注入,因为鸭子打字很容易;但它是静态或动态语言的正确基础DP(不要让我开始“猴子修补”......让我们说它不是我的最爱; - )。

一旦你的类准备好“从外部”接受它的依赖,而不是让它们硬编码,你当然可以使用假的或模拟版本的依赖项来使测试更容易和更快地运行 - 但是这也开辟了其他可能性。例如,如果当前设计的类的状态很复杂且难以设置,请考虑State design pattern:您可以重构设计,以便状态存在于单独的依赖项中(您可以设置并注入根据需要)和类本身主要负责行为(更新状态)。

当然,通过这种方式进行重构,你将会引入越来越多的接口(抽象类,如果你使用的是C ++) - 但这完全没问题:“编程到接口是一个很好的原则,而不是实施“。

所以,为了直接解决你的问题,你是对的:测试的难度绝对是设计相当于极端编程所谓的“代码味道”。然而,从好的方面来说,有一个非常明确的途径来重构这个问题 - 你不必拥有一个完美的设计(幸运的是! - ),但可以随时增强它。我建议将这本书Refactoring to Patterns作为此目的的良好指导。

答案 3 :(得分:6)

对我来说,代码应专为可测试性而设计。反过来说,我认为不可测试或难以测试代码设计糟糕(无论其美观)。

在您的情况下,也许您可​​以模拟外部依赖项来运行真正的单元测试(孤立地)。

答案 4 :(得分:2)

我会采取不同的方法:代码不是为可测试性设计的,但这并不意味着它必须设计糟糕。设计是竞争 *功能的产物,其中可测试性只是其中之一。每个编码决策都会增加一些 * itilies ,同时减少其他代码。例如,设计可测试性通常会损害其简单性/可读性/可理解性(因为它增加了复杂性)。 良好的设计有利于您的情况中最重要的 *功能

您的代码不是错误,它只是最大化可测试性以外的其他 *功能。 : - )

更新:在我被指责设计为 *可测试性并不重要之前,让我先补充一下

当然,技巧是设计和编码以最大化良好的 *功能,特别是重要的。哪些是重要的取决于您的情况。根据我在我的情况下的经验,设计可测试性是更重要的 *功能之一。

答案 5 :(得分:1)

虽然没有办法确定一个班级是否“精心设计”,但至少你提到的第一件事通常是一个有问题的设计的迹象。

您正在测试的类不依赖于数据库,而是应该依赖于一个对象,该对象的唯一责任是获取该数据,可能使用Repository或DAO等模式。

至于第二个原因,它并不一定突出了班级的糟糕设计;它可能是测试设计的问题(没有固定超类型或辅助工具,您可以在其中设置多个测试中使用的依赖关系)和/或整体架构(即不使用工厂或控制反转来注入相应的依赖关系) )

此外,您可能不应该为依赖项使用“真实”对象,而是测试双精度。这有助于确保您正在测试该类的行为,而不是其依赖性的行为。我建议你研究一下模拟框架。

答案 6 :(得分:1)

理想情况下,你的大多数类都是可单元测试的,但是你永远不会达到100%,因为你必须至少有一个专用于将其他类绑定到整个程序的位。 (最好的情况是,这可能只是一个明显和平凡正确的地方,但并非所有代码都是令人愉快的。)

答案 7 :(得分:0)

可能 有一个理想的解决方案供您考虑......私人配件

最近,我担任过一个角色,我们大量使用它们来避免你所描述的情况 - 依赖于主要数据存储中维护的人工数据。

虽然不是最简单的技术实现,但在这样做之后,您将能够轻松地在被测试的方法中将私有成员硬定义为您认为应该拥有的任何条件,并且直接来自单元测试代码(所以没有从数据库加载)。你也可以在不违反班级保护等级的情况下完成目标。

然后它是基本断言&正常验证所需条件。