测试驱动开发是否将重点放在设计上?
我对TDD感触良多。虽然我相信测试,但是对于推动我的开发工作的测试的想法却有疑问。
当我们编写代码来满足为接口而编写的一些测试以满足当前需求时,我们可能会将重点从构建可维护的代码,简洁的设计和声音体系结构转移到其他方面。
我有一个驱动问题,而不是测试问题。有什么想法吗?
解决方案
回答
始终保持平衡:
TDD过多,我们最终会得到有效的代码,但是很难进行工作。
太多的"可维护的代码,简洁的设计和良好的体系结构",我们最终会遇到使宇航员陷入编码瘫痪的架构
在所有事情上要节制。
回答
不。
如果做对了,测试驱动开发就是设计工具。
我希望我们能原谅我链接到我自己的博客条目的原因,其中讨论了测试驱动开发的陷阱,这些陷阱之所以出错,仅是因为开发人员将他们的测试视为测试。
在先前的项目中,开发人员使用了高度破坏性的单例模式,该模式在整个项目中实施了依赖关系,当需求发生变化时,这种依赖关系就破裂了:
TDD was treated as a task, when it should have been treated as an an approach. [...] There was a failure to recognize that TDD is not about tests, it’s about design. The rampant case of singleton abuse in the unit tests made this obvious: instead of the test writers thinking “WTF are these singleton = value; statements doing in my tests?”, the test writers just propagated the singleton into the tests. 330 times. The unfortunate consequence is that the build server-enforced testing was made to pass, whatever it took.
正确执行"测试驱动开发",应使开发人员高度了解设计陷阱,例如紧密耦合,违反DRY(不要重复自己),违反SRP(单一责任原则)等。
如果我们为了通过测试而为测试编写传递代码,则我们已经失败了:我们应该将难以编写的测试当作路标让我们发问:为什么这样做?为什么不依赖其他代码就不能测试此代码?为什么我不能重用此代码?为何单独使用此代码会导致中断?
此外,如果设计是真正干净的,并且代码是真正可维护的,那么为它编写测试为什么并不简单呢?
回答
完成软件的过程分为三个步骤:
- 让它起作用
- 改正它
- 快一点
测试使我们排名第一。代码不仅仅因为测试通过而完成。在开始编写测试/代码之前,最好具有项目结构的一些概念(实用程序,常用访问的对象,层,框架)。在编写代码以使测试通过之后,我们需要重新评估它,以查看可以将哪些部分重构为应用程序的不同方面。 Yuo可以自信地做到这一点,因为我们知道只要测试仍在通过中,代码就仍然可以正常工作(或者至少满足要求)。
在项目开始时,请考虑一下结构。随着项目的进行,请继续评估和重新评估代码,以保持设计不变,或者在不再有意义的情况下更改设计。估算时,必须考虑所有这些项目,否则最终将得到意大利面条字代码,无论是否为TDD。
回答
我完全同意pjz。没有一种正确的软件设计方法。如果我们将TDD推到了极致,除了下一次单元测试之外没有其他任何考虑,则可能会使工作变得更困难。同上一个花了几个月时间在图表和文档上但没有代码的人,开始了一个大型软件项目。
中等。如果有想绘制一个快速图表来可视化代码结构的需求,那就去做吧。如果需要两页,可能是时候开始编写一些代码了。如果我们想在编写测试之前这样做,那该怎么办。我们的目标是工作,提供高质量的软件,而不是绝对符合任何特定的软件开发原则。做对我们和团队有用的事情。寻找可以改进的地方。重复。
回答
我在TDD和单元测试方面相对较新,但是在我使用过的两个附带项目中,我发现它是设计助手,而不是设计替代方案。独立测试和验证组件/子组件的能力使我更容易进行快速更改并尝试新的设计思路。
我在TDD上经历的差异是可靠性。在设计过程的开始而不是以后,就可以在较小级别的组件上确定组件接口的过程是,我拥有可以信任的组件,它们可以更早地工作,因此,我不必担心这些小组件,而可以解决棘手的问题。
而且当我不可避免地需要返回并维护这些小片段时,我可以花更少的时间这样做,这样我就可以回到想要做的工作上。
回答
在大多数情况下,我同意TDD确实提供了一种设计工具。对我而言,最重要的部分是它建立了进行更多更改的能力的方式(我们知道,当我们拥有洞察力的时刻,可以通过删除代码来添加功能)时,风险大大降低了。
就是说,我最近签约的一些算法工作在TDD下受了一些折磨,而没有仔细考虑设计思想。上面关于更安全的重构的声明仍然是一个很大的好处,但是对于某些算法,TDD(尽管仍然有用)不足以使我们获得理想的解决方案。以排序为简单示例。 TDD可以很容易地导致我们选择次优(N ^ 2)的算法(以及大量通过的测试,使我们可以重构为快速排序的算法),例如冒泡排序。 TDD是一种工具,是非常好的工具,但是就像要解决的问题一样,许多东西都需要适当地使用。
回答
在这个问题上,我完全同意你的看法。在实践中,我认为TDD通常会对代码库产生一些非常负面的影响(糟糕的设计,过程代码,没有封装,生产代码中充斥着测试代码,无处不在的接口,难以重构的生产代码,因为所有内容都与许多测试紧密相关等等)。 )。
吉姆·科普林(Jim Coplien)已经就这个话题进行了一段时间的演讲:
Recent studies (Siniaalto and Abrahamsson) of TDD show that it may have no benefits over traditional test-last development and that in some cases has deteriorated the code and that it has other alarming (their word) effects. The one that worries me the most is that it deteriorates the architecture. --Jim's blog
Robert C. Martin和James Coplien之间还就InfoQ进行了讨论,他们就此话题进行了探讨。
回答
总是存在过度使用TDD设计或者前期设计的风险。因此,答案是取决于情况。我更喜欢从用户故事/验收测试开始,这是我的测试将有助于产生的要求的基础。只有在确定了这一点之后,我才开始编写TDD风格的详细单元测试。如果我们唯一的设计和想法是通过TDD进行的,那么我们将冒太多自下而上的方法的风险,这可能会给我们带来出色的隔离性的单元和类,但是当我们尝试将它们集成到用户故事执行任务中时做错了一切可能会感到惊讶。有关更多灵感,请查看BDD。
罗伯特·C·马丁(Robert C. Martin)和詹姆斯·科普利恩(James Coplien)之间记录了一个有关此问题的大辩论,前者是TDD的拥护者,而后者则表示破坏了系统设计。这就是罗伯特对TDD和设计的评价:
"There has been a feeling in the Agile community since about '99 that architecture is irrelevant, we don't need to do architecture, all we need to do is write a lots of tests and do lots of stories and do quick iterations and the code will assemble itself magically, and this has always been horse shit. I even think most of the original Agile proponents would agree that was a silliness."
James Coplien指出,仅从TDD驱动设计就有很大的风险:
"One of the things we see a lot, in a lot of projects, is that projects go south on about their 3rd sprint and they crash and burn because they cannot go any further, because they have cornered themselves architecturally. And you can't refactor your way out of this because the refactoring has to be across class categories, across class hierarchies, and you no longer can have any assurances about having the same functionality."
他还举了一个很好的例子,说明与使用先验知识来驱动架构相比,如果我们测试开车,银行帐户的外观可能会:
"I remember when I was talking with Kent once, about in the early days when he was proposing TDD, and this was in the sense of YAGNI and doing the simplest thing that could possibly work, and he says: 'Ok. Let's make a bank account, a savings account.' What's a savings account? It's a number and you can add to the number and you can subtract from the number. So what a saving account is, is a calculator. Let's make a calculator, and we can show that you can add to the balance and subtract from the balance. That's the simplest thing that could possibly work, everything else is an evolution of that. If you do a real banking system, a savings account is not even an object and you are not going to refactor your way to the right architecture from that one. What a savings account is, is a process that does an iteration over an audit trail of database transactions, of deposits and interest gatherings and other shifts of the money. It's not like the savings account is some money sitting on the shelf on a bank somewhere, even though that is the user perspective, and you've just got to know that there are these relatively intricate structures in the foundations of a banking system to support the tax people and the actuaries and all these other folks, that you can't get to in an incremental way. Well, you can, because of course the banking industry has come to this after 40 years. You want to give yourself 40 years? It's not agile."
有趣的是,TDD支持者和TDD反对者都在说我们需要预先设计。
如果有时间,请观看视频。这是两位很有影响力的专家之间的精彩讨论,而且只有22分钟的时间。
回答
我的思考方式是,首先编写我们想要的代码。
一旦有了目标代码的样本(现在什么都不做),看看是否可以在其上放置测试支架。
如果无法做到这一点,请找出为什么不能做到这一点。
在大多数情况下,这是因为我们做出了错误的设计决策(99%),但是如果不是这样(1%),请尝试以下操作:
- 确定我们需要遵守哪些疯狂的要求,以免测试代码。我们可以了解问题,重新设计API。
- 如果其他人决定此要求,请与他/她讨论。他们可能有充分的理由提出要求,一旦我们知道了他们的理由,就可以完善设计并使之可测试。如果现在还不行,那么我们都可以重新设计需求,并且两者都会变得更好。
在拥有目标代码和测试支架之后。实施代码。现在,我们甚至拥有了通过自己的测试时就知道自己的进步程度的优势(这是一个很好的动力!)
从个人经验来看,测试可能是多余的唯一情况是,当我们制作早期原型时,因为那时我们仍不太了解问题,无法准确地设计或者测试代码。