我最近(在上周)开始了一个实验,其中我尝试在我正在使用TDD原理的项目中编写新功能.在过去,我们的方法是一种适度敏捷的方法,但没有严格的严谨性.当方便时,单元测试就在这里和那里进行.全面测试覆盖的主要障碍是我们的应用程序具有复杂的依赖关系网.我选择了一个方便隔离的功能来尝试我的实验; 细节并不重要,可能具有商业敏感性,足以说这是一个简单的优化问题.
到目前为止,我发现:
那么问题是"有没有人建议减少上述问题的影响?".我毫不怀疑,嘲弄框架将是有利的; 但是目前我已经在努力尝试一些似乎只产生漫无边际码的东西.
编辑#1:
谢谢大家的考虑答案.我承认我在几个星期五晚上的啤酒之后写了我的问题,所以在某些地方它是模糊的,并没有真正表达我真正想要的情绪.我想强调一点,我确实喜欢 TDD的哲学,并且发现它适度成功,但由于我列出的原因也令人惊讶.我有机会睡觉,并在下周用新鲜的眼睛再次看问题,所以也许我可以通过混乱来解决我的问题.然而,他们都不是非先发者.
让我更担心的是,一些团队成员拒绝尝试任何你称之为"技术"的东西,而不是"完成它".我担心,残留的外观将被视为反对该过程的黑色标记,而不是证明它需要完全(即使用模拟框架和强烈的DI)以获得最佳结果.
RE"TDD不一定意味着测试优先":( womp,btreat)
我在这个问题上发现的每一篇文章中的"黄金法则"都是"红色,绿色,重构".那是:
我很好奇人们如何在不遵循最初编写的TDD核心原则的情况下进行测试驱动开发.我的同事称中途住宅(或根据您的观点采用不同且同样有效的方法)"测试验证开发".在这种情况下,我认为创造一个新术语 - 或者可能将其从其他人手中夺走并为此获得信誉 - 是有用的.
用于测试数据的RE DSL :( Michael Venable)
我很高兴你这么说.我确实看到一般表单在整个项目范围内越来越有用,因为所讨论的应用程序维护了一个非常复杂的对象图,通常,测试它意味着运行应用程序并在GUI中尝试.(由于上面的商业敏感性原因,不会将游戏放弃,但它基本上与有向图上的各种指标的优化有关.但是,涉及许多警告和用户可配置的小部件.)
能够以编程方式设置有意义的测试用例将有助于各种情况,可能不限于单元测试.
RE God Objects:
我觉得这样,因为一个班级似乎占据了大部分功能集.也许这很好,而且它确实很重要,但它引起了一些人的注意,因为它看起来就像那些没有沿着这些线路开发的旧代码,并且似乎违反了SRP.我认为不可避免的是,某些类主要用作众多不同封装功能之间的接缝,而其他类只能接缝.如果它会是这样的话,我想我需要做的是从这个明显的上帝对象中尽可能多地清除逻辑,并将其行为重新作为所有分解部分之间的连接点.
(对于主持人:我已将我对帖子的回复添加到此处,因为评论字段不够长,无法包含我想要的详细信息.)
编辑#2(大约五个月后):
好吧,我觉得在仔细考虑问题一段时间之后更新一些想法可能会更好.
最后我最终放弃了TDD方法,我很遗憾地说.但是,我觉得有一些具体和合理的理由,我很乐意在下次有机会时继续使用它.
TDD的无懈可击的重构心态的结果是,当我简单地看一下我的代码时,领导开发者宣称绝大部分都是毫无意义的并且需要去做,我并不感到非常沮丧.虽然不得不抛弃大量艰苦的工作而感到遗憾,但我确实看到了他的意思.
出现这种情况是因为我从字面上理解了"代码到接口"的规则,但继续编写试图代表现实的类.很久以前我第一次发表声明:
类不应该试图代表现实.对象模型应该只尝试解决手头的问题.
...我从那以后经常重复; 对我自己和任何会倾听的人.
这种行为的结果是执行函数的类的对象模型,以及重复类功能的镜像接口集.有了这个指向我,经过一段短暂但强烈的阻力之后,看到了光线,没有任何问题删除大部分.
这并不意味着我相信"接口的代码"是无聊的.它的意思是,当接口代表真实的业务功能时,编码到接口主要是有价值的,而不是看起来像现实生活的微缩副本的某些想象的完美对象模型的属性,但不考虑它的唯一含义生活要回答你最初提出的问题.TDD的优势在于它不能生产这样的模型,除非偶然.因为它首先提出问题并且只关心得到答案,所以不涉及你的自我和系统的先验知识.
我现在正在散步,所以我最好完成这个,并且说我很想再次尝试TDD,但是对可用的工具和策略有更好的概述,并会尽我所能来决定如何我想在跳进去之前去做.也许我应该将这个华夫饼干移植到它所属的博客上,一旦我有更多的话要说.
Car*_*ter 11
TDD并非嘲笑.有时候,嘲弄会促进测试的开发,但是如果你在TDD的第一次传递中开始使用模拟,那么你可能没有得到最好的实践介绍.
根据我的经验,TDD不会导致上帝的物体; 恰恰相反.TDD将我带到了做少事的类,并与更少的其他类交互,更少的依赖.
没有测试就不能编写代码的限制往往会阻碍将功能分解为独立单元的机会.在实践中,同时为这么多功能进行测试和编写测试太困难了.
这对我来说听起来不太合适.您不是要同时为许多功能编写测试; 您正尝试一次为一个功能编写一个测试.编写该测试后,即可通过.当它通过时,你会让它变得干净.然后你编写下一个测试,可能会推动相同功能的进一步开发,直到功能完成,并清理.然后,为下一个功能编写下一个测试.
在编写代码之前编写测试需要在解决问题之前完全理解问题的每个复杂性.这似乎是一个矛盾.
再说一次:写一个测试.这需要完全理解一个特征的一个方面.它需要它,并以可执行的形式具体地表达它.
就像你对问题的全面回应一样,听起来你没有使用TDD很长时间,你可能没有使用任何可能有助于TDD过程的工具,并且你正在为生产线代码比测试代码行.
更具体地说,每一点:
1:TDD鼓励设计不多于或少于它所拥有的设计,即"YAGNI"(你不需要它)的方法.这是"做得很轻".您必须与"正确行事"保持平衡,即将适当的SOLID设计概念和模式合并到系统中.我采用以下经验法则:在第一次使用一行代码时,让它工作.在对该行的第二个引用上,使其可读.第三,让它变得坚固.如果一行代码仅由另一行代码使用,那么在那时放入完全SOLID设计没有多大意义,将代码分解为可以插入和交换的接口抽象类出.但是,一旦开始获得其他用途,您必须具备回溯并重构代码的规则.TDD和敏捷设计完全是关于重构的.这是擦; 瀑布也是如此,它只需花费更多,因为您必须一直回到设计阶段才能做出改变.
2:再次,这是纪律.单一责任原则:一个对象应该做一个特定的事情,并且是系统中唯一做这件事的对象.TDD不允许你懒惰; 它只是帮助你找到你可以懒惰的地方.此外,如果你需要创建一个类的许多部分模拟,或者许多功能强大的完整模拟,你可能会错误地构建对象和测试; 你的对象太大,你的SUT有太多的依赖关系,和/或你的测试范围太宽.
3:不,不.它要求您在编写测试套件时考虑所需的内容.这里是ReSharper(MSVS)等重构助手的真正亮点; Alt + Enter是您的"执行"快捷方式.假设您正在编写一个新类,它将写出一个报告文件.你做的第一件事是新建一个该类的实例."等等",ReSharper抱怨说,"我找不到那个班级!"."所以创造它",你说,按Alt + Enter.它就这样做了; 你现在有一个空的类定义.现在,您在测试中编写了一个方法调用."等等,"ReSharper喊道,"那个方法不存在!",然后你再按Alt + Enter说"再创建它".你刚刚通过测试编程; 你有新逻辑的骨架结构.
现在,您需要一个要写入的文件.首先输入一个文件名作为字符串文字,知道当RS抱怨时你可以简单地告诉它将参数添加到方法定义中.等等,那不是单元测试.这需要您创建的方法来触摸文件系统,然后您必须返回文件并通过它以确保它是正确的.所以,你决定通过一个Stream; 它允许您传入一个完全与单元测试兼容的MemoryStream.有其中TDD影响的设计决策; 在这种情况下,决定是让课程更加SOLID,以便进行测试.同样的决定使您可以灵活地将数据传输到您希望的任何地方; 进入内存,文件,网络,命名管道等等.
4:敏捷团队通过协议制定计划.如果没有协议,那就是一个块; 如果团队被阻止,则不应编写任何代码.要解决阻止,团队负责人或项目经理会做出命令决策.在证明错误之前,这个决定是对的; 如果它最终错了,它应该很快,所以团队可以走向一个新的方向,而不必回溯.在您的具体情况下,让您的经理做出决定 - 犀牛,Moq,无论如何 - 并执行它.其中任何一个都比手写测试模拟好几千.
5:这应该是TDD的真正优势.你有一个班级; 它的逻辑是一团糟,但它是正确的,你可以通过运行测试证明它.现在,您开始重构该类以获得更多SOLID.如果重构不会改变对象的外部接口,那么测试甚至不必改变; 你只是清理一些测试不关心的方法逻辑,除非它有效.如果您要更改界面,则更改测试以进行不同的调用.这需要纪律; 因为正在测试的方法不存在,所以很容易直接测试不再起作用的测试.但是,您必须确保对象中的所有代码仍然得到充分运用.代码覆盖工具可以在这里提供帮助,它可以集成到CI构建过程中,如果覆盖范围不符合要求,则可以"破坏构建".然而,覆盖的另一面实际上是双重的:首先,为覆盖范围增加覆盖率的测试是无用的; 每个测试必须证明代码在某些新颖的情况下按预期工作.另外,"报道"不是"运动"; 您的测试套件可以执行SUT中的每一行代码,但它们可能无法证明逻辑线在每种情况下都能正常工作.
总而言之,TDD在我第一次学习它时会给予我什么,不会给我什么,这是一个非常有力的教训.这是一个编码道场; 任务是写一个罗马数字解析器,它将采用罗马数字字符串并返回一个整数.如果你理解罗马数字的规则,这很容易预先设计,并可以通过任何给定的测试.但是,TDD规则可以非常容易地创建一个类,该类包含测试中指定的所有值及其整数的Dictionary.它发生在我们的道场.这是擦; 如果解析器的实际规定要求是它只处理我们测试的数字,那么我们没有做错任何事; 系统"有效",我们不会浪费任何时间设计一些在一般情况下工作的更精细的东西.然而,我们新的Agilites看着泥潭,并说这种做法是愚蠢的; 我们"知道"它必须更聪明,更强大.但是我们呢?这是TDD的力量和弱点; 你可以设计一个或多于满足用户声明要求的系统,因为你不应该(通常不能)编写不符合或证明支付给你的系统的某些要求的代码.法案.
虽然我做了很多后期开发测试,但这样做有一个重大问题; 您已经编写了生产代码,并希望以其他方式对其进行测试.如果现在测试失败了,谁错了?如果是测试,则更改测试以断言程序当前正在输出的内容是否正确.嗯这没多大用处; 你刚刚证明了系统输出它一直有的东西.如果它是SUT,那么你有更大的问题; 你有一个你已经完全开发的对象没有通过你的新测试,现在你必须撕开它并改变它来做它.如果这是你迄今为止对这个对象的唯一自动测试,谁知道你将通过这一个测试你会打破什么?相反,TDD强制您在合并任何将通过该测试的新逻辑之前编写测试,因此您具有回归验证代码; 在开始添加新代码之前,您有一套测试证明代码符合当前要求.因此,如果在添加代码时现有测试失败,则会破坏某些内容,并且在通过已经存在的所有测试和所有新测试之前,您不应该为发布提交该代码.
如果您的测试中存在冲突,那就是一个块.假设您有一个测试证明给定的方法返回给定A,B和C的X.现在,您有一个新的要求,并且在开发测试时您发现现在相同的方法必须在给定A,B和时输出Y. C.那么,先前的测试对于证明系统以旧方式工作是不可或缺的,因此更改该测试以证明它现在返回Y可能会破坏基于该行为的其他测试.要解决此问题,您必须澄清新要求是旧行为的更改,还是从接受要求错误地推断出其中一个行为.
我非常推荐你的方法继续一段时间,然后阅读Gerard Mezaros的书,xUnit测试模式,并尝试应用他的指导方针.TDD是一条漫长而曲折的道路,开始看到它的好处需要相当长的一段时间.我对您的一些担忧的简短回答如下:
| 归档时间: |
|
| 查看次数: |
1443 次 |
| 最近记录: |