如何避免使用TDD创建不良设计

我最近(在最后一周)开始了一个实验,我试图在我正在使用TDD原则的项目中编写一个新功能。过去,我们的做法是一个适度敏捷的方法,但没有非常严格。单元测试发生在这里和那里当它很方便。综合测试覆盖的主要障碍是我们的应用程序具有复杂的依赖网络。我选择了一个方便墙壁试用我的实验的功能;细节并不重要,可能对商业敏感,足以说这是一个简单的优化问题。

到目前为止,我发现:

对于我来说,TDD似乎鼓励漫游,不明显的设计形成。不经过测试而不能编写代码的限制往往会阻止机会将功能转换为独立单元。在实践中,同时对许多功能进行测试和编写测试是太困难的
> TDD倾向于鼓励创建“神对象”,因为你已经为类x编写了很多嘲笑类,但对于类y来说很少,所以在类x还应该实现特征的时候似乎是合乎逻辑的而不是把它留给y类。
在写代码之前编写测试需要您在解决问题之前对每个复杂的问题有完整的了解。这似乎是一个矛盾。
>我无法让团队在旁边开始使用一个嘲笑的框架。这意味着,只是为了测试一个特定功能而创建的cruft的扩散。对于每一个测试方法,你都会需要一个假的,唯一的工作是报告被测试的类被称为任何应该的。我开始发现自己写的类似于DSL的类似于实例化测试数据的DSL。
>尽管上述担忧,TDD已经产生了一个很少的神秘错误的工作设计,不同于我习惯的开发模式。重新构建这个结果的庞大的混乱需要我暂时放弃TDD,只是完成它。我相信测试将继续执行该方法的正确性。尝试TDD重构运动,我觉得会增加更多的cruft。

那么问题是“有没有人有任何建议来减少上述问题的影响”。我毫不怀疑,一个嘲笑的框架将是有利的;然而目前我已经在推动我的运气,试图看似只是产生漫游代码。

编辑#1:

谢谢大家的答案。我承认我在几个星期五晚上的啤酒后写了我的问题,所以在某些地方,它是模糊的,并没有真正表达我真正的意图。我想强调,我喜欢TDD的理念,并发现它适度成功,但也令我惊讶的是我列出的原因。我有机会睡觉,下周用新鲜的眼睛再次看着问题,所以也许我可以通过混乱来解决我的问题。然而,他们都不是非首发者。

我更关心的是,一些团队成员抵制尝试任何可以称之为“技术”的东西,有利于“完成”。我感到担心的是,这个cruft的出现将被视为黑色的过程,而不是证明它需要完全完成(即愚蠢的框架和强大的DI),以取得最佳效果。

RE“TDD不一定意味着先测试”:(womp,btreat)

我在这个问题上发现的每一个文字中的“黄金法则”是“红,绿,重因子”。那是:

写一个必须失败的测试
编写通过测试的代码
>重构代码,使其以最为实用的方式通过测试

对于如何想象做测试驱动开发而不遵循原始写作的TDD的核心原则,我很好奇。我的同事称之为中途之家(或者根据您的观点采用不同的,同样有效的方法)“测试验证的开发”。在这种情况下,我认为创造一个新术语 – 或者可能将其从别人身上窃取,并为此而付出代价 – 是有用的。

RE DSL测试数据:(Michael Venable)

我很高兴你说我确实看到一般形式在项目范围内越来越有用,因为有关的应用程序维护一个非常复杂的对象图,通常测试它意味着运行应用程序并在GUI中尝试。 (由于上述的商业敏感性原因,不会让游戏消失,但从根本上说是有针对性地对各种指标进行了优化,但是有很多注意事项和用户可配置的小部件涉及。)

能够以编程方式设置有意义的测试用例将有助于各种情况,可能不限于单元测试。

RE神对象:

我觉得这样,因为一个类似乎占用了大部分功能集。也许这很好,这真的很重要,但它引起了一些眉毛,因为它看起来就像以前没有开发的旧代码,似乎违反了SRP。我想这是不可避免的,一些类将主要作用在许多不同的封装位之间的接缝功能,而其他类将仅接缝几个。如果是这样的话,我想我需要做的就是从这个明显的上帝对象中清除尽可能多的逻辑,并将其行为重写为所有分解出来的部分之间的连接点。

(给主持人:我已经将回复添加到此处的帖子,因为注释字段不足以包含我想要的详细信息。)

编辑#2(约5个月后):

嗯,我觉得在仔细研究这个问题一段时间之后,可以更新一些更多的想法。

最终我终于放弃了TDD方式,我很抱歉地说。不过,我觉得有一些具体和合理的理由,而且我下一次获得机会的时候都准备好继续下去。

TDD的unapologetic重构心态的结果是,当我仔细看看我的代码时,我并没有很失望,主角说,绝大多数是毫无意义的,需要去的。虽然要摆脱一大堆辛苦的工作,但是我看到了他的意思。

这种情况出现了,因为我从字面上把“代码到接口”的规则,但继续写作试图代表现实的类。很久以前我先发表了这个声明:

课堂不应该试图代表现实。对象模型只能试图解决手头的问题。

…我曾多次重复对我自己和任何其他人会听。

这种行为的结果是执行功能的类的对象模型,以及重复类的功能的镜像集接口。有了这个指出,经过一个短暂但强烈的抵抗时期,看到了光,没有删除大部分的问题。

这并不意味着我相信“接口的代码”是双栈。它的意思是,当接口代表真实的业务功能时,编码到接口是非常有价值的,而不是一些想象中的完美对象模型的属性,它们看起来像是现实生活的微型副本,但不认为它的唯一含义生活正在回答你原来问的问题。 TDD的实力是,除了机会之外,它不能生成这样的模型。由于它首先提出一个问题,只关心获得答案,您的自我和系统的先前知识就不会涉及。

我现在正在漫游,所以我会尽力完成这一切,只是说我再次尝试尝试TDD,但是可以更好地了解可用的工具和策略,并尽力决定如何在跳进之前想要去想,也许我应该把这个华夫饼移植到一个属于它的博客里,一旦我有更多的话要说。

就像您所遇到的问题一样,您似乎没有使用TDD,您可能没有使用任何可能有助于TDD过程的工具,而您将更加重视生产线生产线比测试代码行。

更具体到每个点:

1:TDD鼓励一个设计,不能少于它所拥有的,也就是“YAGNI”(你不会需要它)的方法。那就是“做光”。您必须将其与“正确”进行平衡,即将适当的SOLID设计概念和模式纳入系统。我采取以下经验法则:在第一行使用一行代码,使其工作。在对该行的第二个引用上,使其可读。在第三,使它固体。如果一行代码只被其他一行代码使用,那么在当时完全实施SOLID设计就没有什么意义了,将代码分解成可插入和交换的接口抽象类出。但是,一旦开始获得其他用途,您必须遵守纪律规定并重新编写代码。 TDD和敏捷设计都是重构。这是擦瀑布也是如此,它只是花费更多,因为你必须一直回到设计阶段来做出改变。

2:再一次,这是纪律。单一责任原则:一个对象应该做一个特定的事情,并且是系统中唯一可以做到这一点的对象。 TDD不允许你懒惰;它只是帮助你找到你可以懒惰的地方。另外,如果你需要创建一个类的很多部分模拟,或者是很多功能齐全的全部模拟,那么你可能正在构建对象并且测试不正确;您的对象太大,您的SUT有太多依赖关系,和/或您的测试范围太广泛。

3:不,不行您需要考虑在编写测试套件时需要什么。在这里,像ReSharper(对于MSVS)的重构助手真的闪耀着; Alt Enter是你的“做”快捷方式。假设您正在开发一个新的类,将会写出一个报告文件。你做的第一件事是新建一个类的实例。 “等等”,ReSharper抱怨说,“我找不到那个班!” “所以创建它”,你说,点击Alt Enter。这样做你现在有一个空类定义。现在你在测试中写一个方法调用。 “等等,”ReSharper哭了,“那个方法不存在!”,你再说一下“再创建它”,再按一次Alt键。你刚刚按照测试程序进行编程;你有新逻辑的骨架结构。

现在,您需要一个写入的文件。您首先输入文件名作为字符串文字,知道当RS抱怨时,您可以简单地告诉它将参数添加到方法定义中。等等,这不是单元测试。这需要您创建的方法来触摸文件系统,然后您必须将文件恢复并通过它,以确保它是正确的。所以,你决定通过一个流;这允许您传入一个MemoryStream,这是完全单元测试兼容的。 TDD在哪里影响设计决策?在这种情况下,决定是让课堂上更加SOLID,以便可以进行测试。同样的决定使您能够灵活地将数据管理在将来的任何地方;进入内存,文件,通过网络,命名管道,无论如何。

4:敏捷团队通过协议计划。如果没有协议,那是一个块;如果团队被阻止,则不应写入代码。为了解决这个问题,团队领导或项目经理做出了一个命令的决定。这个决定是对的,直到被证明是错误的;如果最终出错,它应该这么快,所以团队可以走一个新的方向,而不用回溯。在您的具体情况下,让您的经理作出决定 – 犀牛,莫克,无论如何 – 并强制执行。任何一个都比手写测试嘲笑好一千分之一。

5:这应该是TDD的真正实力。你有一个班;它的逻辑是一团糟,但它是正确的,你可以通过运行测试证明它。现在,您开始重构该类更多的SOLID。如果重构不改变对象的外部接口,那么测试甚至不需要改变;你只是清理一些方法逻辑,测试不关心,除了它的工作。如果您更改界面,则更改测试以进行不同的调用。这需要纪律;这是非常容易的只是一个不再工作的测试,因为被测试的方法不存在。但是,您必须确保对象中的所有代码仍然被充分执行。代码覆盖工具可以在这里帮助,如果覆盖率不高于鼻烟,它可以集成到CI构建过程中并“打破构建”。然而,覆盖面的另一面实际上是双重的:首先,覆盖范围增加的测试是无用的;每个测试都必须证明代码在一些新情况下按预期工作。另外,“覆盖”不是“运动”;您的测试套件可能会执行SUT中的每一行代码,但它们可能无法证明在每种情况下都有一行逻辑工作。

所有这一切,在我第一次学习的时候,TDD将会和不会做什么给我一个非常有力的教训。这是一个编码的dojo任务是写一个罗马数字解析器,它将使用一个罗马数字字符串并返回一个整数。如果您了解罗马数字的规则,这很容易设计,可以通过任何测试。然而,TDD学科可以非常容易地创建一个类,其中包含测试中指定的所有值的字典及其整数。它发生在我们的道场。这是擦如果解析器的实际规定的要求是只处理我们测试的数字,那我们没有错,系统“工作”,而且我们并没有浪费任何时间来设计一些比较详细的,在一般情况下工作的东西。然而,我们新的Agilites看着这个噩梦,并表示这种做法是愚蠢的;我们“知道”它必须更聪明,更健壮。但是我们呢这是TDD的力量和弱点;您可以根据用户规定的要求,设计出更多或更少的系统,因为您不应(并且经常不能)编写不符合或验证由系统提供给您的系统的某些要求的代码法案。

虽然我做了相当多的后期开发测试写作,但这是一个很大的问题。您已经编写了生产代码,希望以其他方式对其进行测试。如果现在失败了,谁错了?如果是测试,那么您更改测试来确定程序当前正在输出的内容是正确的。那没那么多用处?您只是证明系统输出了它总是有的。如果是SUT,那么你有更大的问题;你有一个你已经完全开发出来的对象,不通过你的新测试,现在你必须撕开它并改变东西,使它这样做。如果这是您迄今为止对此对象的唯一自动测试,谁知道您将通过此测试打破什么?相反,TDD在引入任何将通过该测试的新逻辑之前强制您编写测试,因此您具有回归验证代码;在开始添加新的代码之前,您有一套测试证明代码符合当前要求。所以,如果现在的测试在添加代码时失败了,那么你打破了一些东西,你不应该提交这个代码,直到它通过所有已经存在的测试和所有新的。

如果你的测试有冲突,那就是一个块。假设你有一个测试,证明一个给定的方法返回X给定A,B和C.现在,你有一个新的要求,在开发测试你发现现在相同的方法必须输出Y,当给定A,B和那么,以前的测试是证明系统工作的旧方法的一个组成部分,所以改变这个测试来证明它现在返回Y可能会破坏基于这个行为的其他测试。要解决这个问题,你必须澄清一点,新的要求是从旧的行为发生变化,或者是从接受要求中不正确地推断出一个行为。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结