TDD:我做对了吗?


14

我是一名新程序员(只学习了大约一年),为了达到更好的目标,我最近才了解TDD。我想养成使用它的习惯,因为它似乎很有帮助。我想检查并确保正确使用它。

我在做什么:

  1. 想一想我需要的一种新方法。
  2. 为该方法创建一个测试。
  3. 失败测试。
  4. 写方法。
  5. 通过测试。
  6. 重构方法。
  7. 重复。

我正在为我编写的每种方法执行此操作,是否有一些我不应该使用的方法?后来,我通常会想到一种以不同的方式或情况测试我现有方法的方法。我应该进行我想到的这些新测试,还是由于每种方法已经具有自己的测试,我不应该打扰吗?我可以对我的代码进行过度测试,我想这是我主要的担忧。

编辑

另外,我只是想知道这件事。当做类似GUI的事情时,在那种情况下TDD是必要的吗?就个人而言,我想不出该如何编写测试。


5
您已经比经验丰富的专业人员说他们正在测试所有东西(但是没有)要好得多。
扬尼斯2012年

您所描述的不是TDD 的精神

1
您可能需要研究ATDD或BDD。
Dietbuddha 2012年

也许从更高的位置开始-考虑您需要的新模块

Answers:


16

在我看来,您所描述的工作流程并不是TDD 的精神

肯特·贝克斯(Kent Becks)在亚马逊上写的书的提要说:

很简单,测试驱动的开发旨在消除对应用程序开发的恐惧。尽管有些恐惧是健康的(通常被视为告诉程序员“当心!”的良心),但作者认为,恐惧的副产品包括无法吸收建设性批评的暂定,脾气暴躁和缺乏沟通的程序员。当编程团队购买TDD时,他们会立即看到积极的结果。他们消除了工作中的恐惧感,并且更有能力应对面临的困难挑战。TDD消除了暂定特征,它教程序员进行交流,并鼓励团队成员进行批评。但是,即使作者也承认,脾气暴躁也必须单独解决!简而言之,TDD背后的前提是应该不断测试和重构代码。

实用TDD

形式化的自动化测试,尤其是单元测试,每个类的每个方法都像反模式一样糟糕,什么也没测试。有一个平衡。您是否正在为每种setXXX/getXXX方法编写单元测试,它们也是方法!

测试还可以帮助节省时间和金钱,但是不要忘记,它们花费了时间和金钱来开发,而且它们是代码,因此它们花费了时间和金钱进行维护。如果他们因缺乏维护而萎缩,那么他们承担的责任多于收益。

像这样的所有事物,都有一个平衡,只有你自己才能定义。任何一种教条都可能更正确。

好的度量标准是对业务逻辑至关重要的代码,并且可以根据不断变化的需求进行频繁修改。这些东西需要自动化的正式测试,这将是巨大的投资回报。

您将很难找到许多以这种方式工作的专业商店。花钱测试所有实际上不会在执行简单的烟雾测试后就改变的东西,在商业上是没有意义的。编写正式的.getXXX/.setXXX方法的自动化单元测试是一个很好的例子,完全浪费时间。

自从有人指出程序测试可以令人信服地表明存在错误,但是永远不能证明它们不存在以来,已经过去了二十年。在认真引用了这一广为人知的言论后,软件工程师回到了日常工作中,继续完善他的测试策略,就像昔日的炼金术士一样,继续完善他的金相纯化。

- Edsger W. Djikstra。(写于1988年,所以现在已经接近4.5年了。)

另请参阅此答案


1
那几乎解决了我所关心的问题。我觉得我不应该像以前那样测试所有方法,但是不确定。看来我可能仍需要阅读有关TDD的更多信息。
cgasser 2012年

@kevincline大多数时候setXXX/getXXX根本不需要:)
筹码

1
当您记住琐碎的getXXX并弄错了它,或者在getXXX中引入延迟加载并弄错了它时,您会知道有时您确实想测试您的getter。
Frank Shearar

13

你很亲密 尝试以稍微不同的方式思考。

  1. 想一想我需要的一种新行为。
  2. 为该行为创建一个测试。
  3. 失败测试。
  4. 编写新方法或扩展现有方法。
  5. 通过测试。
  6. 重构代码。
  7. 重复。

不要为每个属性自动创建getter和setter方法不要想一个完整的方法,而是编写涵盖所有功能的测试。尝试将属性封装在类中并编写方法以提供所需的行为。让您的方法演变成一个好的设计,而不是尝试预先计划它们。请记住,TDD是设计过程,而不是测试过程。与其他设计流程相比,它的优势在于无需进行自动回归测试,而无需将纸张丢进垃圾箱。

另外,请记住Bob叔叔的TDD的三个规则

  1. 除非要通过失败的单元测试,否则不允许编写任何生产代码。
  2. 不允许编写任何足以导致失败的单元测试。编译失败就是失败。
  3. 您不能编写任何足以通过一项失败的单元测试的生产代码。

1
@Zexanima:您的情况比一年后的大多数人都要好。只是试图将您指向下一步。
pdr 2012年

2
我认为您链接到这三个规则;尽管听起来像是田园诗般的,但在任何人都会遇到的所有生产车间中,有99%的生产车间格格不入,而且非常不切实际。

1
@FrankShearar 或它可以被视为原教旨主义极端主义的不切实际的胡言乱语,无视批发。我曾在有这种教条式态度的商店里工作,他们从字面上接受了教条而错过了重点。编写不以实际方式测试任何实际代码的测试,最后仅测试Mocking和Dependency Injection框架有能力混淆最重要的内容。

1
@pdr某种事物的精神与该事物的教条形式化正典完全相反。拥有一种哲学是一回事,而将其转变成一种宗教是一回事。在黑白教条性宗教术语中,对TDD的讨论比不多。这三个规则在表达方式上听起来是教条和宗教性的,而听到的是“ 测试”,“测试”,“测试口头禅”,对OP等人来说,他们按字面意义对待它们,弊大于利。我反对弗兰克(Frank),两极分化的言论对事业造成的伤害大于好处。

2
我的观点是,教条主义来自盲目接受福音。采取两极分化的说法,尝试一下,使它逼出您的舒适区。如果您不尝试三点全有或全无三点的极端方法,则无法评估TDD中的权衡,因为您将没有数据
Frank Shearar

5

可以添加到其他响应中的几件事:

  1. 诸如过度测试之类的事情。您想确保单元测试尽可能少地重叠。没有必要让多个测试在同一段代码中验证相同的条件。另一方面,当您重构生产代码并且许多测试与该部分重叠时,您将不得不返回并修复所有这些测试。如果它们不重叠,那么一项更改最多只会破坏一项测试。

  2. 仅仅因为您想到了编写测试的更好方法,我就不会回到那里开始重写它。这可以追溯到不断编写和重写相同类/函数的个人,因为他们试图使其完美。它永远不会是完美的,所以继续前进。当您发现更好的方法时,请将其放在脑海中(或添加到测试的注释中)。下次您到那儿时,您会发现切换到新方式的直接好处是重构的时候了。否则,如果功能已完成并且您继续前进,并且一切正常,请使其保持正常工作。

  3. TDD致力于提供即时价值,而不仅仅是确保每个功能都可测试。添加功能时,请先询问“客户端需要什么”。然后定义一个接口,为客户提供所需的东西。然后实施使测试通过所需的一切。TDD几乎就像测试用例场景(包括所有“假设”)一样,而不是简单地编写公共功能并测试每个功能。

  4. 您询问有关测试GUI代码的问题。查找“低级对话框”和“ MVVM”模式。这两种方法背后的想法是,您创建了一组“视图模型”类,它们实际上没有特定于UI的逻辑。但是,这些类将具有通常属于UI的所有业务逻辑,并且这些类应100%可测试。剩下的就是一个非常薄的UI Shell,是的,通常情况下,该Shell没有测试覆盖,但是到那时它应该几乎没有逻辑。

  5. 如果您有大量的现有代码(如很少有人建议的那样),则不应绝对在任何地方开始添加单元测试。这将带您一辈子,并且不会为80%稳定的类添加单元测试而受益,这些类在不久的将来(或不久的将来)不会改变。但是,对于新工作,我确实发现将TDD开发与所有代码一起使用是非常有益的。完成后,您不仅会获得带有自动化测试的套件,而且实际开发会带来巨大的好处:

    • 通过考虑可测试性,您将编写耦合性更低,模块化程度更高的代码
    • 通过首先考虑您的公共合同,您将获得更干净的公共接口
    • 在编写代码时,与运行整个应用程序并试图强制执行正确的路径相比,验证新功能需要毫秒。我的团队仍然发布错误处理代码,而该错​​误处理代码甚至因为无法获得正确的条件集而尚未执行过。令人惊奇的是,我们后来在质量检查中浪费了多少时间,这些情况确实发生了。是的,很多这样的代码被人们认为是“一旦完成烟雾测试,将来就不会有太大的变化”。

1

有一些尚未测试的方法,即那些测试。但是,对于在编写初始代码之后要添加的一些测试(例如边界条件和其他值),要说些什么,以便在一个方法上可以进行多个测试。

尽管您可以对代码进行过度测试,但通常是有人想要测试输入的所有可能排列,听起来似乎不像您在做什么。例如,如果您有一个接受字符的方法,那么您是否针对每个可能输入的值编写测试?IMO,那将是您过度测试的地方。


啊,好的。那不是我在做什么 我通常只是想到了另外一种情况,在我已经进行了最初的测试之后,我可以重新测试我的方法。我只是在确保那些“额外”测试值得进行,或者是否已经结束。
cgasser 2012年

如果您以足够小的增量进行工作,通常可以合理地确定您的测试确实有效。换句话说,测试失败(出于正确的原因!)本身就是在测试测试。但是这种“合理肯定”的水平不会像被测试的代码那样高。
Frank Shearar

1

通常,您做对了。

测试是代码。因此,如果您可以改善测试,请继续进行重构。如果您认为可以改进测试,请继续进行更改。不要害怕用更好的测试来代替。

我建议在测试代码时,避免指定代码应如何执行其工作。测试应查看方法的结果。这将有助于重构。某些方法不需要显式测试(即简单的getter和setter),因为您将使用这些方法来验证其他测试的结果。


我也正在为吸气剂和吸气剂编写测试,因此感谢您的提示。这样可以省去我一些不必要的工作。
cgasser 2012年

“某些方法不需要显式测试(即简单的getter和setter)”-您从未复制/粘贴getter和setter并忘记更改其后面的字段名称吗?关于简单代码的问题是,它需要简单的测试-您真正节省了多少时间?
pdr 2012年

我并不是说该方法未经测试。通过确认是否设置了其他方法或在实际测试过程中进行检查即可。如果getter或setter不能正常工作,则测试将失败,因为属性设置不正确。您可以免费进行暗含测试。
施莱伊斯(Schleis)2012年

Getter和setter测试不会花费很长时间,因此我可能会继续进行它们。但是,我从不复制并粘贴任何代码,因此不会遇到该问题。
cgasser 2012年

0

我对TDD的看法是,该工具创造了一个“点击式”样式开发人员的世界。仅仅因为工具为每种方法都创建了一个测试存根,并不意味着您应该为每种方法编写测试。有些人将TDD重命名为BDD(行为驱动的开发),在这种情况下,测试的粒度要大得多,并且旨在测试类的行为,而不是每个方法都很少。

如果您设计测试以按预期使用该类,那么您会开始获得一些好处,尤其是当您开始编写比每种方法用得多的测试时,尤其是当您开始测试那些方法的交互性时方法。我想您可以将其视为为类而不是方法编写测试。无论如何,您仍然必须编写“验收测试”,对各种方法进行组合,以确保在将它们一起使用时不存在矛盾或冲突。

不要将TDD与测试相混淆-并非如此。TDD的设计使您可以编写代码来满足您的要求,而不是测试方法。对于那些盲目地为每种方法编写测试代码的人来说,这是一个微妙而重要的观点。您应该编写测试来确保您的代码能够实现您想要的功能,而不是您编写的代码按其预期的方式工作。

BDD v TDD右侧有一些不错的链接。去看一下。


0

当您开始学习TDD时,是的,您应该盲目地遵循教条主义的方法:不编写任何代码,除非通过了失败的测试,并且仅编写了足以使测试失败的测试(并且出于正确/预期的原因而失败) 。

一旦了解了TDD的含义,那么您就可以确定某些事情不值得测试。这是您应遵循的所有方法相同的方法,日本武术称之为“ shuhari ”。(该链接还说明了在没有老师的情况下如何才能完成学习的各个阶段,我怀疑这是大多数人必须学习的方法。)


0

我相信您测试过度。

我从事TDD已有多年经验,根据我的经验,有效执行TDD可以带来两个主要好处:

  • 提供快速反馈
  • 启用重构

提供快速反馈

特别是对于动态语言,我可以在不到一秒钟的时间内执行相关测试。当磁盘上的源文件更改时,我有文件系统观察程序自动运行这些测试。因此,我几乎没有等待测试的时间,而且我立即知道编写的代码是否按预期进行。因此,TDD导致一种非常有效的工作方式。

启用重构

如果您有一个好的测试套件,则可以安全地重构,因为您对如何设计系统有了新的认识。

良好的测试套件可让您在代码中转移责任,同时仍然对代码在迁移后能够按预期工作充满信心。而且您应该只需对测试代码进行少量更改就可以做到这一点。

如果您为系统中的每个方法编写测试,则很可能无法轻松重构代码,代码的每个重构都需要对测试代码进行大量更改。您甚至可以确定测试代码仍然可以按预期运行吗?还是您不小心在测试代码中引入了一个错误,从而导致了生产代码中的一个错误?

但是,如果您像pdr的答案中所建议的那样,在编写测试时着重于行为而不是方法,那么您将拥有在重构系统时所需的更改更少的测试。

或如Ian Cooper在本演示文稿中所说(我引用了内存,所以可能未正确引用):

您编写新测试的原因应该是添加新行为,而不是添加新类


-2

您应该测试每种公共方法。

这里的问题是,如果您的公共方法很小,那么您可能会暴露过多的信息。将每个属性公开为的getXXX()实际做法实际上破坏了封装。

如果您的公共方法实际上是该类的行为,则应该对其进行测试。如果不是,它们不是好的公共方法。

编辑: 博士的答案比我的完整得多。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.