TDD和封装优先级冲突

时间:2010-04-11 15:18:44

标签: php unit-testing tdd phpunit encapsulation

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

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

3 个答案:

答案 0 :(得分:8)

在TDD圈子中有一个非常标准的答案。如果您希望隐藏和直接测试的类中有功能,则应sprout a class使用该功能。这是TDD如何改进您的设计的一个很好的例子。

在原始类中,无关的功能已经消失,包含在发芽类中,因此原始类的设计更简单,更好地符合Single Responsibility Principle。在发芽类中,提取的功能是 raison d'etre ,因此它适合于公开,因此它是可测试的,无需仅测试修改。

答案 1 :(得分:7)

尊重卡尔·曼纳斯特的好答案,在走上卡尔建议的道路之前,至少应该考虑一些弊端。

其中最重要的是:我们使用封装来最小化带有最大变化传播概率的潜在依赖关系的数量。在您的情况下,您在类中封装了私有方法:它们不可用于其他类,因此不存在对它们的潜在依赖性:您对它们所做的任何更改的成本都会降至最低,并且传播到其他类的可能性很低。类。

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

通过执行此操作,您可以删除对这些方法形成依赖关系的其他类的障碍,如果任何其他类使用它们,这可能会增加对这些方法进行查询的成本。

你可以判断这个未成年人的未成年人和一个有价值的价格,以便能够测试你的私人方法,但至少要知道它。在少数情况下,它可能确实值得,但如果您在整个代码库中实现这一点,那么您将大大增加这些依赖关系形成的可能性,从而将维护周期的成本增加到未知程度。 / p>

出于这些原因,我不同意Carl的意见,他的建议是“...... TDD如何改善您的设计的一个很好的例子。”

此外,他说,“在原始类中,无关的功能已经消失,包含在萌芽的类中,因此原始类的设计更简单,更好地符合单一责任原则。”

我认为移动的功能根本就不存在,“无关紧要。”而且,“更简单”,是一个定义不明确的:当然,类的简单性可能与其大小成反比但是这并不意味着最简单的类系统将是最简单的系统:如果是这种情况,所有类只包含一个方法,系统将拥有大量的类;可以认为,删除这种层内多个方法的层次结构会使系统更加复杂。

单一责任原则(SRP)是众所周知的主观因素,完全取决于观察者的抽象程度。完全不是从类中删除方法会自动改进其与SRP的一致性。具有10种方法的Printer类具有在类的抽象级别进行打印的单一职责。其中一个方法可能是checkPrinterConnected(),一个可能是checkPaper();在方法层面,这些显然是单独的职责,但它们并不自动建议将该类分解为更多的类。

Carl完成了,“在萌芽阶层,提取的功能是它的存在理由,因此它适合公开,因此它是可测试的,没有仅测试修改。”功能的重要性(它是存在的理由) 'etre-ness)不是公开适当性的基础。功能公开的适当性的基础是最小化暴露给客户端的接口,使得类的功能可用,同时客户端的功能实现的独立性最大化。当然,如果你只是将一个方法移动到发芽类中,那么它必须是公共的。但是,如果要移动多种方法,则必须将这些方法公之于众,这对于客户成功使用该类是必不可少的:这些公共方法可能远不如您希望屏蔽您的某些私有方法重要。客户。 (无论如何,我不喜欢这个,“Raison-d'etre”,因为方法的重要性也没有明确定义。)

Carl建议的另一种方法取决于您设想的系统增长程度。如果它将增长到少于几千个类,那么您可能会考虑使用脚本将源代码复制到新目录,在该复制的源中更改所有“private”,“public”的出现,然后编写对复制的源进行测试。这具有复制代码所需的时间的缺点,但保留封装原始源的好处,但使所有方法在复制版本中可测试。

以下是我为此目的使用的脚本。

此致

Ed Kirwan

!/斌/庆典

rm -rf code-copy

echo创建代码复制...

mkdir代码复制

cp -r ../www code-copy /

for find code-copy -name "*php" -follow;做

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

完成

php run_tests.php

答案 2 :(得分:3)

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

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

当Carl说“你应该发布一个具有该功能的类”时,本文的作者将解释你的测试如何通过使用模拟对象来指导你,如何设计你的类,所以你1)不要需要担心的是无法测试私有部件,更重要的是2)这将如何改善您的设计(我将用卡尔斯的话来解释)以正确的责任发现合作者和角色。

作者将逐步向您介绍一个例子,以明确说明问题。

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

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

引用:

  

许多以TDD开头的人都在努力   抓住依赖关系。至   测试一个物体,你运动一些   行为,然后验证是否   对象处于预期状态。   因为OO设计侧重于   行为,对象的状态是   通常隐藏(封装)。成为   能够验证对象是否表现   像预期的那样,你有时需要   访问内部状态和   介绍暴露的特殊方法   这个状态,就像一个getter方法或者一个   检索内部的属性   状态。

     

除了不想要对象   混乱他们的界面和   暴露他们的私人部分,我们   既不想引入不必要的   与这些额外的getter的依赖关系。   我们的测试会变得太紧张   耦合并专注于实施   的信息。

     

一组敏捷软件开发   来自英国的先驱者   还在努力解决这个问题   他们必须添加额外的getter方法来验证状态   对象他们的经理不喜欢   所有这些打破封装和   声明:我想要没有吸气剂   码! (Mackinnon等,2000&   Freeman et al。,2004)

     

团队提出了这个想法   专注于互动而不是   州。他们创造了一个特殊的对象   取代合作者   被测物体。这些特别的   对象包含的规范   预期的方法调用。他们打电话给   这些对象模拟对象或模拟   简而言之。最初的想法有   经过精炼,产生了几个   所有常见的模拟对象框架   编程语言:Java(jMock,   EasyMock,Mockito),.NET(NMock,   RhinoMocks),Python(PythonMock,   Mock.py,Ruby(Mocha,RSpec),C ++   (mockpp,amop)。看到   www.mockobjects.com了解更多   信息和链接。