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

TDD和封装优先级冲突

  •  4
  • Hanseh  · 技术社区  · 14 年前

    我刚开始在我的项目中练习TDD。我正在开发一个使用php/zend/mysql和phpunit/dbunit进行测试的项目。我只是对封装和测试驱动方法的想法有点分心。封装背后的想法是隐藏对多个对象功能的访问。更清楚地说,私有和受保护的函数不能直接测试(除非您将创建一个公共函数来调用它)。

    所以我最终将一些私有和受保护的函数转换为公共函数,只是为了能够测试它们。我真的违反了封装的原则,让位于微功能的可测试性。这是正确的方法吗?

    3 回复  |  直到 14 年前
        1
  •  8
  •   Carl Manaster    14 年前

    在TDD循环中有一个相当标准的答案。如果您希望隐藏并直接测试类中的功能,则应该 sprout a class 有了这个功能。这是TDD如何改进设计的一个很好的例子。

    在原始类中,无关的功能消失了,被包装在萌芽的类中,因此原始类的设计更简单,更好地符合 Single Responsibility Principle . 在sprouted类中,提取的功能是 雷森德雷特 因此,它适合公开,因此它可以在不进行仅测试修改的情况下进行测试。

        2
  •  7
  •   Ed Kirwan    14 年前

    关于这一点,卡尔·马纳斯特的回答很好,在走上卡尔建议的道路之前,你至少应该考虑一些缺点。

    其中最重要的一点是:我们使用封装来最小化具有最大变更传播概率的潜在依赖项的数量。在您的例子中,您已经在类中封装了私有方法:它们不可用于其他类,因此没有对它们的潜在依赖性:您对它们所做的任何更改的成本都是最小化的,并且传播到其他类的可能性很低。

    似乎Carl建议将一些私有方法从类移到一个新类中,并将这些方法公开(以便您可以测试它们)。(顺便说一下,为什么不在原来的课堂上公开呢?)

    通过这样做,可以消除其他类对这些方法形成依赖关系的障碍,如果其他类使用这些方法,这可能会增加修改这些方法的成本。

    你可能会认为这是次要的和值得付出的代价,能够测试你的私人方法,但至少要意识到这一点。在少数情况下,这可能确实是值得的,但是如果您在代码库中建立这一点,那么您将大大增加这些依赖性形成的可能性,从而将维护周期的成本增加到未知的程度。

    基于这些原因,我不同意卡尔的观点,他提出的建议是,TDD如何改进你的设计的一个很好的例子。

    此外,他还指出,原始类中的__,外部功能消失了,包裹在萌芽类中,因此原始类的设计更简单,更好地符合单一责任原则。

    我会争辩说,移动的功能根本不是无关的,也就是说,更简单的,是一个没有很好定义的:当然,一个类的简单性与它的大小成反比,但这并不意味着一个最简单的可能类的系统将是最简单的可能系统:如果这是一个在这种情况下,所有类只包含一个方法,一个系统将拥有大量的类;可以说,删除类中多个方法的层次结构层会使系统更加复杂。

    此外,单一责任原则(SRP)是众所周知的主观原则,完全依赖于观察者的抽象水平。从类中删除一个方法并不会自动提高它与SRP的一致性。有10种方法的打印机类在类的抽象级别上具有单一的打印责任。它的一个方法可以是checkprinterconnected(),另一个方法可以是checkpaper();在方法级别,这些方法显然是独立的职责,但它们不会自动建议将类分解为更多的类。

    Carl Finishes,_在萌芽类中,提取的功能是它的存在,因此它适合公开,因此它可以在不进行测试修改的情况下进行测试。_功能的重要性(它是存在于公共中)不是其公开的适当性的基础。公开功能的适当性的基础是最小化向客户机公开的接口,以便类的功能可以使用,而客户机对功能实现的独立性最大化。当然,如果只将一个方法移动到萌芽类中,那么它必须是公共的。但是,如果要移动多个方法,则必须将这些方法公开,这对于客户端成功使用类至关重要:这些公开方法可能远不如您希望保护客户端的某些私有方法重要。(在任何情况下,我都不喜欢这句话,raison-d'etre,短语,因为方法的重要性也没有很好的定义。)

    卡尔建议的另一种方法取决于你对系统增长的设想有多大。如果它将增长到少于几千个类,那么您可以考虑使用一个脚本将源代码复制到一个新目录,将复制源中所有发生的,私有的,私有的,更改为,公共的,然后针对复制的源编写测试。这会降低复制代码所需的时间,但保留原始源代码的封装,同时使所有方法都可以在复制的版本中进行测试。

    下面是我用于此目的的脚本。

    当做,

    埃德基尔万

    !/BI/BASH

    RM-射频代码副本

    echo正在创建代码副本…

    代码拷贝

    CP-R../WWW代码副本/

    因为我在 find code-copy -name "*php" -follow

    sed -i 's/private/public/g' $i
    

    完成

    php运行测试.php

        3
  •  3
  •   koen    14 年前

    我刚刚读了一篇关于让模拟对象驱动你设计的伟大文章:

    http://www.mockobjects.com/files/usingmocksandtests.pdf

    当Carl说“你应该用这个功能开发一个类”时,本文的作者解释了你的测试如何通过使用模拟对象来指导你如何设计你的类,这样你就1)不必担心不能测试私有部分,更重要的是2)这将如何通过(我将解释carls引述)发现具有正确职责的合作者和角色。

    作者一步一步地给你举个例子,让你明白他的观点。

    下面是另一篇采用相同方法的文章:

    http://www.methodsandtools.com/archive/archive.php?id=90

    引文:

    许多人从TDD开始与 掌握依赖关系。到 测试一个物体,你可以锻炼一些 然后验证 对象处于预期状态。 因为OO设计的重点是 行为,对象的状态是 通常隐藏(封装)。成为 能够验证对象的行为 像预期的那样,你有时需要 访问内部状态和 介绍暴露的特殊方法 这种状态,比如getter方法或 检索内部 状态。

    除了不想要东西 混乱的界面和 暴露他们的隐私,我们 也不想介绍不必要的 依赖于这些额外的getter。 我们的测试会变得太紧了 结合并专注于实施 细节。

    一组敏捷软件开发 英国的先驱者是 也在为这件事苦苦挣扎 1999。他们必须添加额外的getter方法来验证状态 物体的他们的经理不喜欢 所有这些破坏的封装和 声明:我不想在 代码!(Mackinnon等人,2000& Freeman等人,2004年)

    团队想出了一个主意 关注互动而不是 状态。他们创造了一个特殊的物体 替换的合作者 测试对象。这些特殊 对象包含的规范 需要方法调用。他们叫 这些对象模拟对象,或模拟 简而言之。最初的想法 被提炼,导致 所有公共的模拟对象框架 程序设计语言:Java(JMOCK) easymock,mockito),.net(nmock, 犀牛),巨蟒(蟒蛇, Mock.py,露比(MOCHA,RSPEC),C++ (莫克普,阿莫普)。见 www.mockobjects.com了解更多信息 信息和链接。