过分关注测试好处总体上是一件坏事吗?

时间:2009-05-24 11:23:04

标签: design-patterns testing tdd

我的意思是,有时建筑师希望以牺牲其他重要力量为代价来简化和提高可测试性。

例如,我正在审查一个非常复杂的应用程序,这是通过广泛使用过度支持测试的设计模式来实现的,例如: IoC,DI,AOP等...... 现在,通常我喜欢这些东西,但是这个系统应该更简单 - 虽然不仅仅是数据库上CRUD的简单Web前端,它仍然没有那么复杂(甚至考虑到一些内部)工作流程,流程等)。另一方面,只是审查代码成为heinie的主要痛苦,几乎不可读(即使它写得很好),编码一定是痛苦的。

实施的复杂性显然违反了KISS(原则,而不是乐队)......并且“唯一”的好处是提高了可测试性,使用测试框架和模拟和... ...

现在,在TDD粉丝跳过我之前,我并没有贬低可测试性的重要性,但我质疑考虑这种特定力量的优势(对所有其他人)。 或者我错过了什么?


我想补充一点 - 在我看来,所有关于“可测试性”的讨论都是针对单元测试,这与整个系统测试不同,并且可能导致在单个单元集成在一起时错过测试。至少,这似乎是用于测试的IoC / DI的重点......
另外,我要指出的是,这个系统(以及我所见过的其他系统)每个接口只有一个具体对象,而IoC / DI仅用于 - 您猜对了 - 用测试模型替换具体对象仅测试。


我觉得有必要在Wikipedia on IoC中添加这句话:

  

虽然程序编程中的危险是以意大利面条代码结束,但使用Inversion of Control时的危险以通心粉代码

结尾
是的,这完全表达了我的感受:D

13 个答案:

答案 0 :(得分:13)

TDD做得好可以提高可读性。 TDD表现不佳,即不考虑其他重要原则,会降低可读性。

我在90年代中期与之合作的人会说:“你总是可以通过增加一层间接来使系统更加灵活。你总是可以通过删除一层间接来简化系统。”灵活性和简单性都是系统的重要特性。这两个原则通常可以和谐共处,但往往它们相互作用。如果你走向一个极端或另一个极端,你就会摆脱这两个原则平衡的理想。

TDD部分是关于测试,部分是关于设计。 TDD表现不佳可能会对灵活性或简单性产生过多影响。它可以推动太多的灵活性。对象变得更加可测试,并且通常更简单,但是域问题的固有复杂性随后被推出对象到对象的交互中。我们获得了灵活性,对于幼稚的眼睛,它看起来好像我们已经获得了简单,因为我们的对象更简单。然而,复杂性仍然存在。它被移出对象,进入对象交互,在那里它更难控制。这里有代码气味可以作为红色标志 - 一个包含数百个小对象而没有更大对象的系统就是其中之一,许多只有一行方法的对象是另一个。

TDD表现不佳也可以向另一个方向发展,也就是说,太简单了。因此,我们首先编写测试来进行TDD,但它对我们的设计影响不大。我们仍然有很长的方法和巨大的对象,而这些代码气味可能会使这个问题出现问题。

现在TDD本质上不会让你在任何一个方向上失去平衡,只要它得到很好的应用。使用其他做法让您保持正轨。例如,在您执行此操作之前绘制您正在执行的操作的图片。显然,不是所有的时间。有些事情太简单了。有些图片值得保存,有些只是草图,可以帮助我们将问题可视化,而且我们在不同程度上主要是视觉学习者。如果你无法画出问题的图片,你就不明白了。

这对TDD有何帮助?这将有助于防止系统在灵活性方面走得太远,远离简单性方面。如果你画一幅画并且它很难看,那就是一面红旗。有时它是必要的,但通常当你画画时,你的思想会很快看到可以简化的东西。该解决方案变得更加优雅和简化,更易于维护,并且更加愉快。如果您不能或不会绘制系统图片,那么您将失去这个使您的软件更加坚固,更优雅,更美观,更易于维护的机会。

应用这一点需要经验,有些程序员永远不会理解良好平衡所提供的价值。没有可以运行的指标可以告诉您在正确的位置。如果有人给你一个规定的方法来达到这个和谐点,他就骗你了。更重要的是,他可能在没有意识到的情况下撒谎。

所以,我对你的问题的回答是'是':在不忘记其他好原则的情况下测试一切。

如果与其他良好做法不平衡,任何好的做法都会让你偏离正轨。

答案 1 :(得分:6)

要回答你的一般问题,我会说“一切都在适度”。强调可测试性当然是一件好事。但不是以排除可读代码或逻辑API为代价的。

答案 2 :(得分:6)

“我错过了什么吗?”

事情有效,不是吗?

更重要的是,你可以证明它有效。

与可测试性相比,为可测试性添加的相对复杂程度并不是非常有趣,并且它可以证明它确实有效。此外,您可以进行更改并证明您没有破坏它。

备选方案(可能有效,也可能无效,无法证明其是否有效,无法在不破坏的情况下进行更改)会将软件的价值降低为零。


修改

“复杂性”是一个很滑的概念。有复杂的客观衡量标准。更重要的是复杂性增加所带来的价值。复杂性的增加为您提供了可测试性,可配置性,后期绑定性,灵活性和适应性。

此外,复杂性的客观度量通常集中在方法内的编码,而不是类和对象之间关系的更大复杂性。复杂性似乎是客观的,但并未在软件架构的所有层面定义。

“可测试性”也很滑。可能存在可测试性的客观测量。然而,大多数情况下,这些都是为了测试覆盖范围。测试覆盖率不是一个非常有意义的指标。生产崩溃的可能性如何随测试覆盖范围而变化?它没有。

您可以将注意力集中在可测试性上。你可以把许多事情归咎于复杂性。如果你密切关注高度可测试的代码,你会发现它也非常灵活,可配置和适应性强。

选择“可测试性”作为“复杂性”的根本原因忽略了这一点。

关键是有许多相互关联的质量因素。 “它有效”是一种总结最重要的方法。其他不太重要的包括适应性,灵活性和可维护性。这些额外因素通常与可测试性相关,并且它们也可以被描述为“复杂性”。

答案 3 :(得分:4)

我见过通过所有单元测试的第一手网站,通过了所有自动化界面测试,通过了负载测试,几乎每次测试都通过了,但很明显,当人类查看时出现问题。

导致代码分析,发现内存泄漏,缓存问题,错误代码和设计缺陷。当遵循多种测试方法并通过所有测试时,这是如何发生的?没有任何“单位”有内存泄漏或缓存问题,只有整个系统。

我个人认为这是因为一切都是为了通过测试而编写和设计的,而不是优雅,简单和灵活的设计。测试中有很多价值。但仅仅因为代码通过测试,并不意味着它是好的代码。这意味着它是“书本智能”代码,而不是“街头智能”代码。

答案 4 :(得分:3)

在我看来,考虑到一个足够大或重要的软件,增加一些复杂性来提高可测试性是值得的。此外,根据我的经验,复杂性难以理解的地方是添加抽象层以包裹一段本身就不可测试的代码(如密封的框架类)。当从可测试性的角度编写代码作为第一原则时,我发现代码实际上易于阅读,并且没有必要复杂。

我实际上非常愿意在可以避免的情况下增加复杂性。我还没有转向DI / IoC框架,例如,更喜欢只在需要测试的地方手动注入依赖项。另一方面,在我最终采用“增加”复杂性的实践 - 比如嘲弄框架 - 我发现复杂程度实际上比我担心的要少,而且收益比我想象的要多。也许,我最终会发现这对于DI / IoC框架也是如此,但我可能不会去那里,直到我有一个足够小的项目进行试验而不会通过学习新东西而不合理地推迟它。

答案 5 :(得分:3)

“或者我错过了什么?”

在可测试代码如何与复杂代码之间存在隐含的直接关系。如果这是你的经历,我会说you're doing it wrong

代码不必更复杂,更易于测试。重构代码以使其更易于测试确实会使代码更灵活,更小巧。这并不一定意味着更复杂(已经是一个加载的术语)或者需要在远处进行动作。

不知道细节,我只能提供通用建议。检查您是否只使用周模式。如果你有一个方法需要很多设置或复杂的方法来覆盖它的行为,那么内部就会有一系列更简单,更确定的方法。提取这些方法,然后您可以更轻松地对它们进行单元测试。

测试不必像其测试的代码一样干净和精心设计。通常,在测试中通常做一些令人讨厌的黑客攻击而不是对代码进行大量重新设计通常会更好。这对于故障测试尤其有用。需要模拟数据库连接失败吗?简单地将connect()方法替换为始终失败的方法。需要知道磁盘填满后会发生什么?将文件open方法替换为失败的方法。有些语言很好地支持这种技术(Ruby,Perl),其他语言则不那么多。通常可怕的风格成为一种强大的测试技术,对您的生产代码是透明的。

我将明确指出的一件事是永远不要将代码放在生产中,这只对测试有用。像if( TESTING ) { .... }这样的东西就出来了。它只会使代码变得混乱。

答案 6 :(得分:3)

可测试产品是一个可以回答有关它的问题的产品。可测试性与质量一样,是多维的和主观的。当我们将产品评估为(不)可测试时,重要的是要认识到可能会增加对某人的可测试性或者给其他人增加不必要的复杂性。

对于程序员来说,具有大量单元测试的产品可能是非常值得测试的,但如果没有自动化的钩子,那么产品可能很难测试测试工具。然而,相同的产品,如果它具有干净的工作流程,优雅的用户界面和日志记录,可以通过交互式黑盒测试仪进行精彩测试。没有任何单元测试的产品可以如此干净和清晰地写入,以便它非常适合检查和审查,这是另一种形式的测试。

我谈论可测试性here。詹姆斯巴赫谈到它here

--- Michael B。

答案 7 :(得分:2)

(这完全是从程序员的角度编写的。对于面向客户的更多答案,我建议Michael Bolton回复。)

如果您正在编写的应用程序是< 10行代码,那么是的,添加测试会大大增加复杂性。你可以看看它并手动测试它,你可能会没事的。在100线,而不是1000线,而不是10,000线,100,000线......等等。

第二个轴是变化。这个海湾基地/永远/会改变吗?多少钱?代码越多,测试的价值就越高。

所以,是的,对于一个150行代码的应用程序,它是一个edi格式到edi格式的转换脚本,以批量模式运行,永远不会改变,重型单元测试可能会过度杀死

通常,对于大型应用程序,我发现将代码更改为可测试可以提高设计和API的质量。因此,如果您正在编写更大的内容或者将迭代开发并且认为(自动化)单元测试具有高成本/低价值,我会认真考虑为什么您认为情况如此。

一种解释是你的老板有模式成瘾。另一个可能是您将模式和测试视为是/否全有或全无的讨论。第三个是代码已经写好了,而且你正在重新编写可重复编写的代码。如果是这种情况中的任何一种,我建议采用手术方法 - 专注于一些可以非常快速地增加价值的高压降压测试。随着代码的进展,慢慢扩展您的测试套件。当你看到价值和简单时,重构模式 - 而不是复杂性。

答案 8 :(得分:1)

这种方法的好处将会回来, IF 应用程序将会变得足够大。否则 - 这只是浪费时间。有时甚至拖放'编码'并遵循SmartUI模式也足够令人满意。

答案 9 :(得分:1)

从描述中可以看出,项目失去了对YANGI的追踪,开发了大型结构,以便在需要时进行测试。

在TDD中,一切都通过测试证明是合理的,因此您需要将所有这些IoC,DI,AOP作为最简单的解决方案,以使现有测试通过或(更有可能)成为过度工程的解决方案保持代码可测试。

我所看到的一个错误导致这种复杂性是希望让测试遵循设计,而不是相反。可能发生的是,保持某种难以测试的设计的愿望导致引入各种解决方法来打开API,而不是开发更简单,更容易测试的API。

答案 10 :(得分:1)

无论好坏,TDD帮助我将我的应用程序分解为更易于管理的组件,在这些组件中,我独立测试项目的能力迫使我保持​​简洁。当我将其他人介绍给我的代码时,这些测试也是一个很好的文档来源。通过测试可以很好地检查应用程序的工作情况,在这些应用程序中,事物被隔离得足够远,这样您就可以将功能部分包裹起来。另一个不错的副产品是,当您在应用程序中使用设计模式时,测试与您使用该模式的其他应用程序具有相似性。

所有这一切,实现让我们说命令模式并且当你知道应用程序只会执行两个函数时只有两个命令真的很愚蠢。现在你已经背负着写一堆测试的困扰。得到了什么?您可以随时测试公共方法,但是如果存在一种模式,则需要处理复杂性,并且必须承担所有必须维护的额外测试的技术债务。

需要考虑的另一个因素是您的团队可以支持的级别架构。是否所有团队成员都对TDD的理解程度相同,或者是否会有少数人能够理解测试?看到一个模拟物体会让某人的眼睛茫然,并且仅仅这样做会成为不能及时完成维护的禁忌因素吗?

最后,应用范围也需要推动设计。为了“纯洁”而复杂,并不是一种好的判断。 TDD不会导致这种情况;相反,缺乏经验可以。

答案 11 :(得分:0)

我不知道你的意思是它几乎不可读,因为即使使用AOP和DI,每个部分都应该易于理解。由于这些技术,理解整体可能会更复杂,但更重要的是能够用模型或文本解释应用程序的工作原理。

我目前正在开发一个没有单一单元测试的应用程序,所以,现在我开始介绍DI以帮助简化测试,但是,这将使其他开发人员更难理解系统,因为可以插入不同的具体类,在你查看app.config文件之前你不会知道哪一个。

这可能导致他们认为代码是不可读的,因为它们不能简单地从一个功能级别流向另一个功能级别,而是必须进行一次侧行以查看要使用的具体类。

但是,从长远来看,这将是一个更加灵活和稳定的系统,所以我认为值得参与一些培训。 :)

您可能只需要了解如何为应用程序获取更好的系统模型,以了解所有内容是如何捆绑在一起的。

答案 12 :(得分:0)

  

我正在审查一个非常复杂的问题   应用,广泛使用   过分偏爱的设计模式   测试,例如IoC,DI,AOP等......

在这种情况下,测试不是问题,它的设计模式和整体架构是错误的,这是Joel Jeff在讨论架构时常被批评的内容宇航员。在这里,我们有一些基于'哇酷架构'的决定,如果1个设计模式是好的,2必须是伟大的,3必须是梦幻般的 - 让我们看看我们可以创建这个应用程序的模式数量。

如果测试对于使这些模式可靠地工作可能是必不可少的(嗯,真的说明了一些),但是你不应该把测试与一些建筑设计很差混淆。

所以,不,随意专注于测试而不用担心 - 例如,极限编程是一种非常简单的开发方法,专注于测试,如果你以这样一种自由形式编写你的应用程序,你可能没有进入你的混乱,你所拥有的不是测试驱动开发的错,而是设计选择。

如果你可以开始废弃它,那么这样做 - 可维护性是软件中最重要的因素,如果它不容易修改那么你可以密封它并重新开始,因为维护它可能会花费你更多。 / p>