代码之家  ›  专栏  ›  技术社区  ›  AviD

太多的关注测试的好处总体上是一件坏事吗?

  •  11
  • AviD  · 技术社区  · 16 年前

    我的意思是,有时架构师寻求以牺牲其他重要力量为代价来简化和改进可测试性。

    例如,我正在回顾一个非常复杂的应用程序,它是通过大量使用设计模式来实现的,这些设计模式过度偏向于测试,例如IOC、DI、AOP等。
    现在,通常我喜欢这些东西,但是这个系统 应该 已经简单多了——虽然不仅仅是数据库上CRUD的一个简单的Web前端,但它仍然不比这复杂多少(甚至考虑到一些内部工作流、流程等)。另一方面,仅仅检查代码就成了海涅的一大痛苦,几乎不可读(即使它写得很好),并且对它进行编码一定是一种痛苦。

    实现的复杂性是一个明显违反了亲吻(原则,而不是乐队)的人…而“唯一”的好处是提高了可测试性,使用测试框架和模拟以及……

    现在,在你的TDD粉丝跳过我之前,我并不轻视可测试性的重要性,但我质疑考虑这种特定力量(对所有其他力量)的至高无上性。
    还是我错过了什么?


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


    我觉得有必要加上这句话 Wikipedia on IoC :

    程序编程中的危险是以意大利面条代码结束的,而使用控制反转的危险是以 通心粉代码

    是的,这正好表达了我的感受:D

    13 回复  |  直到 14 年前
        1
  •  6
  •   John Feminella    16 年前

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

        2
  •  13
  •   Don Branson marios    16 年前

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

    我在90年代中期工作过的一个家伙会说,“你可以通过添加一层间接性使系统更加灵活。你总是可以通过去掉一个间接层来简化一个系统。”灵活性和简单性都是系统的重要品质。这两个原则通常可以和谐地共存,但它们往往是相互对立的。如果你走得太远,走向一个极端或另一个极端,你就会远离理想,这两个原则是平衡的。

    TDD部分涉及测试,部分涉及设计。做得不好的TDD会倾向于灵活性或简单性。它可能会带来太多的灵活性。对象变得更容易测试,而且通常更简单,但是域问题的固有复杂性随后被从对象中推出,进入对象的交互。我们获得了灵活性,对于天真的人来说,它看起来像是我们获得了简单,因为我们的对象更简单。然而,复杂性仍然存在。它被移出对象,进入对象交互,在那里它很难控制。这里的代码气味可以充当红色标志——一个系统有数百个小对象,而没有一个大对象,很多只有一行方法的对象是另一个。

    做得不好的TDD也可以向另一个方向发展,也就是说,向过于简单的方向发展。所以,我们首先编写测试来完成TDD,但是它对我们的设计影响很小。我们仍然有很长的方法和巨大的对象,这些都是代码的味道,可能会使这个问题变得危险。

    现在,只要应用得当,TDD本质上不会让你在任何一个方向失去平衡。用其他的方法来保持你的状态。例如,在做之前先画出你正在做的事情的图片。显然,不是所有的时候。有些事情太简单了。有些图片是值得保存的,有些只是帮助我们形象化问题的草图,而我们在不同程度上主要是视觉学习者。如果你不能画出问题的图片,你就不明白。

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

    应用这一点是有经验的,一些编码人员永远不会理解良好的平衡所提供的价值。没有一个指标可以告诉你你在正确的地方。如果有人给了你一个达到和谐点的既定方法,那他就是在骗你。更重要的是,他可能自欺欺人却没有意识到。

    所以,我对你的问题的回答是“是”:测试所有的东西,不要忘记其他的好原则。

    如果与其他良好实践不平衡,任何好的实践都会使你偏离课程。

        3
  •  6
  •   S.Lott    16 年前

    “我错过什么了吗?”

    对。

    这件事行得通,不是吗?

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

    与它实际工作并且您可以证明它实际工作的事实相比,为可测试性添加的相对复杂度并不是很有趣。此外,您可以进行更改并证明您没有破坏它。

    备选方案(可能或可能不起作用,不可能证明它是否起作用,不破坏它就无法进行更改)将软件的价值降低到零。


    编辑

    “复杂性”是一个很难理解的概念。对复杂性有客观的衡量标准。更重要的是,复杂性的增加创造了价值。增加的复杂性使您具有可测试性、可配置性、后期绑定、灵活性和适应性。

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

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

    您可以将复杂性归咎于对可测试性的关注。你可以把复杂性归咎于很多事情。如果仔细观察高度可测试的代码,您会发现它也具有高度的灵活性、可配置性和适应性。

    挑出“可测试性”作为“复杂性”的根本原因并没有抓住要点。

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

        4
  •  4
  •   Brent Baisley    16 年前

    我看到过通过所有单元测试、通过所有自动化接口测试、通过负载测试、通过几乎所有测试的第一手网站,但是很明显,当被人看到时,这些网站存在问题。

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

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

        5
  •  3
  •   tvanfosson    16 年前

    在我看来,给定一个足够大或重要的软件,增加一些复杂性以提高可测试性是值得的。另外,在我的经验中,复杂度难以理解的地方是,当抽象层被添加到一段本身就不稳定的代码上时(就像密封的框架类)。当从可测试性作为第一原则的角度编写代码时,我发现代码实际上很容易阅读,而且并不比必要的复杂。

    事实上,我很难避免增加复杂性。例如,我还没有转向DI/IOC框架,而是只在测试需要的地方手工注入依赖项。另一方面,我最终采用了一种“增加”复杂性的实践——比如模拟框架——我发现复杂性的数量实际上比我想象的要少,而且好处也比我想象的要多。也许,我最终会发现这对DI/IOC框架也是正确的,但是我可能不会去那里,除非我有一个足够小的项目来进行试验,而不会不合理地通过学习新东西来延迟它。

        6
  •  3
  •   Schwern    16 年前

    “还是我错过了什么?”

    在可测试代码的程度和复杂代码的程度之间存在着隐含的直接关系。如果这是你的经验,我会说 you're doing it wrong .

    代码不必更复杂,也可以更容易测试。将代码重构为更具可测试性,确实倾向于使代码更灵活、更小块。这并不一定意味着更复杂(这已经是一个负荷术语),或者需要在一定距离内采取行动。

    不知道细节,我只能给出一般性的建议。检查你是否不仅仅是在使用本周的模式。如果您有一个方法需要大量的设置或复杂的方法来覆盖它的行为,那么通常会有一系列更简单的、确定性的方法。提取这些方法,然后您可以更容易地对它们进行单元测试。

    测试不必像代码测试那样干净和设计良好。通常情况下,最好在测试中做一些通常是讨厌的黑客行为,而不是对代码进行大量的重新设计。这对于失败测试特别有用。需要模拟数据库连接失败?将connect()方法短暂替换为始终失败的方法。需要知道磁盘满后会发生什么?将文件打开方法替换为失败的方法。有些语言很好地支持这种技术(Ruby、Perl),而其他语言则不太支持。通常可怕的风格变成了一种对生产代码透明的强大的测试技术。

    我要明确地说的一件事是,永远不要将只对测试有用的代码投入生产。任何事情 if( TESTING ) { .... } 就在外面。它只是把代码弄乱了。

        7
  •  3
  •   Michael Bolton    16 年前

    可测试产品是一种提供了回答有关它的问题的机会的产品。可测试性和质量一样,是多维的和主观的。当我们将产品评估为(不)可测试性时,重要的是要认识到对某些人的可测试性可能会被添加到其他人身上,或者对其他人来说不必要的复杂性。

    对于程序员来说,具有大量单元测试的产品可能是非常好的测试工具,但是如果没有自动化的钩子,那么对于测试工具匠来说,该产品可能很难测试。然而,同样的产品,如果它有一个干净的工作流程、一个优雅的用户界面和日志记录,那么它可以被一个交互式黑盒测试人员完美地测试。没有单元测试的产品可能写得很清楚,很容易被检查和审查,这是另一种测试形式。

    我说的是可测试性 here . 詹姆斯·巴赫谈到它 here .

    --- Michael B.

        8
  •  2
  •   Matthew Heusser    14 年前

    (这完全是从程序员的角度写的。对于更面向客户的回答,我建议迈克尔·博尔顿的回答。)

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

    第二个轴是变化的。这个海湾基地/曾经/改变过吗?多少钱?代码更改得越多,测试就越有价值。

    所以,是的,对于一个150行的代码应用程序,它是一个从EDI格式到EDI格式的转换脚本,它以永远不会改变的批处理模式运行,繁重的单元测试可能会被过度终止。

    一般来说,对于大型应用程序,我发现将代码更改为可测试的可以提高设计和API的质量。因此,如果您正在编写更大的东西,或者将迭代开发这些东西,并且认为(自动化)单元测试具有高成本/低价值,那么我将认真研究一下为什么您认为这是事实。

    一种解释是你的老板对模式上瘾。另一种可能是,您将模式和测试视为是/否全部或无全部讨论。第三个问题是代码已经被编写出来了,而你害怕的是重新编写的代码是可测试的。如果是这样的话,我建议采用外科手术的方法——专注于一些快速增加价值的高强度降压测试。随着代码的发展,缓慢地增长您的测试套件。当您看到价值和简单性(而不是复杂性)时,重构为模式。

        9
  •  1
  •   Arnis Lapsa    16 年前

    这种方法的好处会回来, 如果 应用程序将足够大。否则,这只是浪费时间。有时,即使拖放“编码”并遵循SmartUI模式也足够令人满意。

        10
  •  1
  •   Yishai    16 年前

    从描述上看,这听起来像是项目失去了对杨氏的跟踪,开发了大型结构,以便在需要时进行测试。

    在TDD中,一切都是通过测试来证明的,所以事实上您拥有所有这些IOC、DI、AOP要么是使现有测试通过的最简单的解决方案,要么(更可能)是使代码可测试的超引擎解决方案。

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

        11
  •  1
  •   David Robbins    16 年前

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

    尽管如此,实现命令模式是非常愚蠢的,当你知道应用程序只会执行两个函数时,你只会有两个命令。现在你已经为编写一系列测试而感到苦恼了。得到了什么?您总是可以测试公共方法,但是有了一个适当的模式,您就有了复杂的事情要处理,并且在您必须维护的所有额外的测试中产生了技术债务。

    另一个需要考虑的因素是您的团队可以支持什么级别的架构。所有团队成员是否都具有相同的TDD理解水平,或者是否会有少数人能够理解测试?看到一个模拟的物体会让人的眼睛变得呆滞吗?仅仅是这一点就成了不及时完成维护工作的阻碍因素吗?

    最后,应用范围也需要驱动设计。为了“纯粹”而复杂不是很好的判断。TDD不会造成这种情况;相反,缺乏经验也会造成这种情况。

        12
  •  0
  •   James Black    16 年前

    我不知道您所说的几乎不可读是什么意思,因为即使在使用AOP和DI时,每个部分都应该很容易理解。由于这些技术的存在,理解整个应用程序可能会更加复杂,但这更多的是能够用模型或文本来解释应用程序是如何工作的。

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

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

    但是,从长远来看,这将是一个更加灵活和稳定的系统,所以我认为有必要进行一些培训。:)

    您可能只需要了解如何为应用程序获得更好的系统模型,以了解所有东西是如何联系在一起的。

        13
  •  0
  •   gbjbaanb    16 年前

    我在复习一个非常复杂的 用途广泛 过度偏爱的设计模式 测试,如IOC、DI、AOP等…

    在这种情况下,测试不是问题所在,它的设计模式和整个体系结构都有问题,这通常受到 Joel Jeff 在与建筑宇航员的讨论中。这里,我们有一些基于“哇酷架构”的设计方案,如果1个设计模式是好的,2个必须是好的,3个必须是好的——让我们看看我们可以用多少模式来创建这个应用程序。

    如果测试可能是使这些模式可靠工作的必要条件(嗯,这确实说明了一些问题),但是您不应该把测试好与一些体系结构设计差混为一谈。

    所以,不,不用担心就可以专注于测试——例如,极限编程是一种非常简单的开发方法,它专注于测试,如果你以这样一种自由形式编写你的应用程序,你可能不会陷入困境,你不是测试驱动开发的错误,而是所做的设计选择。

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