代码之家  ›  专栏  ›  技术社区  ›  Extrakun

很难进行单元测试的类是否设计错误?[关闭]

  •  17
  • Extrakun  · 技术社区  · 14 年前

    我现在正在一个应用程序上进行单元测试,这个应用程序是在我开始努力进行单元测试之前编写的。我意识到我编写的类很难进行单元测试,原因如下:

    1. 依赖于从数据库加载数据。这意味着我必须在表中设置一行以运行单元测试(而不是测试数据库功能)。
    2. 需要很多其他外部类才能使我正在测试的类进入初始状态。

    总的来说,设计似乎没有什么问题,只是它耦合得太紧(这本身就是一件坏事)。我认为,如果我已经用每个类编写了自动化的测试用例,因此确保不会为类工作堆积额外的依赖项或耦合,那么类的设计可能会更好。

    这个理由成立吗?你的经历是什么?

    8 回复  |  直到 14 年前
        1
  •  13
  •   Péter Török    14 年前

    是的,你是对的。一个类,它是 不可单元测试 很难进行单元测试(几乎总是)设计得不好(和往常一样,也有例外,但这些都是很少见的——imho最好不要试图用这种方式来解释问题)。缺乏单元测试意味着很难维护——当您修改其中的任何内容时,您无法知道是否已经破坏了现有的功能。

    此外,如果它与程序的其他部分(共同)依赖,那么即使在看起来不相关的、远离代码的部分中,它的任何更改也可能会破坏某些东西。

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

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

        2
  •  11
  •   ChrisF    14 年前

    是的,松耦合的设计可能更好,但最终需要数据来进行测试。

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

        3
  •  11
  •   Alex Martelli    14 年前

    我发现 dependency injection 是最有助于使我的代码可测试的设计模式(而且,通常也可以重用,并适应不同于我为其设计的原始环境的上下文)。我的JAVA使用同事崇拜 Guice ;我主要用python编程,所以我通常“手动”进行依赖项注入,因为duck类型使它变得容易;但是对于静态或动态语言来说,它是正确的基本dp(不要让我开始使用“monkey patching”……让我们说它不是我最喜欢的;-)。

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

    当然,通过这样的重构,您将引入越来越多的接口(抽象类,如果您使用C++)——但这完全正确:这是一个很好的原则,“编程到接口,而不是实现”。

    因此,为了直接解决您的问题,您是对的:测试的困难绝对是设计上相当于极端编程所称的“代码味道”。不过,从好的方面来说,有一条非常清晰的路径可以将这个问题重构掉——您不必从一个完美的设计开始(幸运的是!-,但可以随你的移动而增强。我推荐这本书 Refactoring to Patterns 作为这一目标的良好指导。

        4
  •  6
  •   Pascal Thivent    14 年前

    对我来说,代码应该是 为可测试性而设计 . 另一方面,我认为不可测试或难以测试的代码设计得很糟糕(不管它有多漂亮)。

    在您的案例中,也许您可以模拟外部依赖关系以实际运行 单元测试 (孤立地)

        5
  •  2
  •   Bert F    14 年前

    我要换个方法:代码不是为 可测试性 但这并不意味着 设计不当 . 设计是竞争的产物 ** 其中 可测试性 只是其中之一。每个编码决策都会增加 *伊迪斯 同时减少其他。例如,设计 可测试性 一般危害其 简单性/可读性/可理解性 (因为它增加了复杂性)。一 好的 设计最重要 ** 你的处境。

    你的代码不是 坏的 它只是最大化另一个 ** 以外 可测试性 . -)

    更新:在我被指控为 *可测性 并不重要

    当然,诀窍是设计和编码以最大限度地提高产品质量 ** 尤其是重要的。哪些是重要的取决于你的情况。以我在这种情况下的经验,为 可测试性 更重要的是 ** .

        6
  •  1
  •   Donal Fellows    14 年前

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

        7
  •  1
  •   Diego Mijelshon    14 年前

    虽然没有一种方法可以确定类是否“设计良好”,但至少您提到的第一件事通常是设计有问题的迹象。

    您测试的类不应该依赖于数据库,而应该依赖于一个对象,该对象的唯一职责是获取数据,可能使用类似于存储库或DAO的模式。

    至于第二个原因,它不一定突出显示类的不良设计;它可能是测试设计(没有fixture父类型或帮助器,您可以在其中设置几个测试中使用的依赖项)和/或总体架构(即不使用工厂或控制反转来注入相应的数据)的问题。突发事件)

    另外,您可能不应该为依赖项使用“真实”对象,但是测试会加倍。这有助于确保您正在测试那个类的行为,而不是它的依赖项的行为。我建议您研究模拟框架。

        8
  •  0
  •   Hardryv    14 年前

    可以 有一个理想的解决方案供你考虑…私人访问器

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

    虽然这不是最简单的实现技术,但是这样做之后,您将能够轻松地在被测试的方法中定义私有成员,以满足您认为它们应该具备的任何条件,并直接从单元测试代码(因此不从数据库加载)中定义私有成员。此外,您还可以在不违反类保护级别的情况下完成目标。

    然后,它是基本的断言和验证,用于正常情况下的所需条件。