TDD-由内而外由内而外


53

使用TDD 构建应用程序Inside In与使用Inside Out构建应用程序有什么区别?

这些是我阅读的有关TDD和单元测试的书:
测试驱动开发:示例
测试驱动开发:实用指南:实用指南在Microsoft中开发
高质量PHP框架和应用程序的实际解决方案
。 NET
xUnit测试模式:重构测试代码
单元测试的技巧:以.Net为例
,以测试为指导的不断增长的面向对象软件中的示例 --->因为JAVA不是我的主要语言,所以这真的很难理解:)

通常,几乎所有的人都解释了TDD的基础知识和单元测试,但是很少提及构建应用程序的不同方式。

我注意到的另一件事是,这些书中的大多数(如果不是全部)在编写应用程序时都会忽略设计阶段。他们更多地专注于快速编写测试用例并让设计自行出现。

但是,我遇到了xUnit测试模式中的一段,该段讨论了人们处理TDD的方式。有2所学校在外面,在内部内部

遗憾的是,这本书没有对此进行详细说明。我想知道这两个案例的主要区别是什么。
什么时候应该使用它们中的每一个?
对于TDD初学者来说,哪个更容易掌握?
每种方法的缺点是什么?
有没有专门讨论该主题的材料?


XUnit测试模式网站上描述了这两种方法:xunitpatterns.com/Philosophy%20Of%20Test%20Automation.html。很奇怪他们不在书中。
guillaume31 2012年

Answers:


45

“由内而外”和“由外而内”是相当少见的用语,我更常听到/读过有关经典学校伦敦学校的信息

  • 由内而外(经典学校,自下而上):您从组件/班级(内部)开始,然后将测试添加到需求中。随着代码的发展(由于重构),出现了新的协作者,交互和其他组件。TDD完全指导设计。

  • 由外而内(伦敦学校,自上而下“模仿者TDD”,如Martin Fowler所称):您了解上层的交互和合作者(尤其是高层交互)并从那里开始(顶层),嘲笑必要的依赖项。对于每个完成的组件,您都将移至先前模拟的协作者,并在那里再次从TDD开始,创建实际的实现(尽管使用了,但由于抽象而不需要使用)。请注意,从外而内的方法与YAGNI原理非常吻合。

两种方法都不是唯一的 ; 他们俩的位置取决于您的工作。在大型企业解决方案中,设计的某些部分来自于建筑师(或预先存在),一个可能始于“伦敦风格”方法。另一方面,当您不确定不确定代码的外观(或代码是否适合系统的其他部分)时,从一些低端组件开始并让它变得更容易随着更多测试,重构和要求的引入而发展。

无论您使用哪种方式,大多数情况下都是视情况而定。

为了进一步阅读,有一篇Google小组文章,其中有趣地讨论了这种区别(可能是如何产生的)以及为什么伦敦可能不是最合适的名字。


2
有趣。您如何得出这样的结论,即TDD中的外部是“模拟主义者” TDD?我非常喜欢从外到内的思想,设计和测试(请参阅softwareonastring.com/2015/01/10/…),但Fowler的文章坚定地将我带入Fowler的古典主义阵营。尽管嘲笑者可能总是使用“由外而内”的方法,但您不能扭转局面,说“由外而内”的设计和测试嘲笑者TDD。古典主义者的TDD-ers也可以实践并由外而内。
Marjan Venema

@jimmy_keen-从外而内,您是否随时用更高版本的实际实现替换了更高级别测试中的模拟?还是将它们保留为模拟依赖项,然后将整个生产代码作为集成测试来使用?
咆哮者

1
我不同意Classic / Mockist和Inside-Out / Outside-In是相关的。它们是正交的。您可以同时使用Inside-Out / Outside-In。
Daniel Kaplan

同意丹尼尔。您正在将两个不同的分类法进行比对。尽管“由内而外”的发展通常与伦敦(模拟主义)学校有关,但并非总是如此。
guillaume31

我认为这不是对“由内而外”流程的正确描述。这是关于从公共接口进行测试,而不要耦合到内部。
mcintyre321 '18

15

简短答案:通常,这取决于您的编码偏好和团队合作方式。

由内而外的编码很棒,因为您总能做些事情。不利之处在于,它不一定能帮助您到达一个截然不同的地方。用这种方式来规划路线比较困难。同样,从外部编写代码也有不利的一面,即不一定具有快速迭代开发的好处,也不一定看到代码结构深处可能产生的所有机会和模式。

我已经相信这两种开发风格都很重要,并且在团队中混合使用多种风格实际上是有帮助的。这个想法是,内而外对于创建积木很重要,而思维中的外在则提供了表格的结构和方向。

我的推理的一部分来自非常流行的一种思想流派,该流派目前促进迭代开发,这通常是“由内而外”开发的代名词。我相信只要您走不远,迭代开发就很棒。但是我认为,与单​​纯的迭代过程相反,大局思维对于某些类型的创新以及进入不太明显的地方是无价的。正确地管理,由内而外的结合在一起可能是非常有效的组合。


8

您应该将C#中的敏捷原则,模式和实践添加到该列表中。我不知道他为什么最后坚持使用“ C#”。这些书根本不是语言,它在亚马逊上没有获得5星的唯一原因是因为对他的示例的C#风格感到失望的人们。

作者主张,只要有可能,就应该尝试从外而内编写代码,并严重依赖于演化设计,我同意他的说法。他的理由是,随着我们添加功能,我们的设计将不断发展。如果从添加了功能的低级组件开始,我们将意识到这些组件没有按照我们希望它们做的方式工作,或者需要四处移动。这可能会变得相当昂贵,尤其是如果每次将功能从一个类转移到另一类时,都需要在所有单元测试项目中进行相同的移动。

另一方面,如果您首先确定应用程序应该做什么,则可以为外部接口编码。随着功能的增加和被测代码的增加,您可以将应用程序重构为更多的类,但是在进行这种重构工作时,编写的原始单元测试仍然有效。因此,您可以完全从外部开始,继续重构为越来越多的低级类,同时向这些内部类中添加其他单元测试,但是您几乎不必四处移动并重写单元测试。

但是,如果您确定了您的应用程序将需要的特定低级子系统(也许您的公司已经在其他应用程序中需要这种子系统),那么这是时候首先从低级构建块开始,然后再开始在此基础上构建应用程序。


7

正如我所看到的那样,“由内而外”开发的概念实际上有两个层次。Gerard Meszaros简要地它们描述为“由外而内的设计 ”和“由内而外/由内而外的编码 ”。

  • 第一层是组织和流程层。从里到外的设计与自上而下(瀑布/泰勒风格)和自下而上的设计相反。采用由内而外的方法,我们专注于最终用户的角度。我们从故事测试,ATDD或BDD测试开始,然后“向内”推断技术测试和代码。因此,从外而内的设计通常是您在敏捷环境中所要做的。丹·诺斯(Dan North)对BDD,自顶向下,自底向上和从外而内的方法进行了精彩的演讲

  • 第二层是技术层面,与应用层有关。从里到外的编码基本上意味着从UI开始,然后向内到达中央层(通常是业务/域层)。这与从中心层开始到最后对外部层进行编码的由内而外的编码相反。

因此,您可以采用由外而内的编码或由内而外的编码进行由外而内的设计。

我不同意Meszaros的观点,是他将内部-外部编码与集成测试相关联,认为在内部-外部环境中“我们实际上并没有独立于内部软件来测试外部软件”。但是我相信没有什么可以阻止您这样做的。您可以通过模拟内层对象来完美选择测试外层对象,即使这些对象的生产代码已经存在。您只需要在现有具体对象之上添加接口和模拟,而无需编写接口,对其进行模拟,然后稍后像使用外部编码一样创建实现。

换句话说,模拟或古典风格的TDD是IMO与由内而外的编码正交的问题。您可以完美地使用模拟主义者的风格以及由内而外的方法。这背后的原因是,模拟/经典风格与代码依赖性有关,而由内而外的编码则与应用层有关

另一个重要的事情是,依赖关系不仅存在于各个层,而且还存在于同一层的对象之间。例如,您可能想从中央业务层中的一个对象开始(由内而外的方法),并使用模拟将您的对象与与其交谈的其他业务层对象隔离。IoC经常发生这种情况-对象所依赖的抽象通常在同一层中声明,而具体实现则在另一层中。

罗伯特·“鲍伯叔叔”·马丁在他的文章“ 干净的体系结构 ”中简要提到了由内而外的编码及其与解耦的体系结构不一定冲突的方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.