TDD - 初学者问题和绊脚石

时间:2010-01-14 18:38:28

标签: python tdd testdrivendesign

虽然我已经完成了大部分代码的单元测试,但我最近才拿到了Kent Beck的TDD副本。我一直对我做出的某些设计决定表示遗憾,因为它们阻止了应用程序的“可测试性”。我读完了这本书,虽然其中一些看起来很陌生,但我觉得我可以管理它并决定在我当前的项目上试用它,这个基本上是一个客户端/服务器系统,两个部分通过它进行通信。 USB。一个在小工具上,另一个在主机上。该应用程序是在Python中。

我开始了,很快就纠缠在一堆重写和小测试中,我后来认为它并没有真正测试任何东西。我扔掉了大部分,现在有了一个有效的应用程序,测试已经凝结成只有2个。

根据我的经验,我有几个问题,我想问一下。我从New to TDD: Are there sample applications with tests to show how to do TDD?获得了一些信息,但有一些我想要回答/讨论的具体问题。

  1. Kent Beck使用他添加并列出的列表来指导开发过程。你怎么做这样的清单?我最初有一些项目,如“服务器应该启动”,“服务器应该中止,如果渠道不可用”等但他们混合,最后现在,它只是“客户端应该能够连接到服务器”(其中包含服务器启动等。)。
  2. 你如何处理重写?我最初选择了基于命名管道的半双工系统,这样我就可以在自己的机器上开发应用程序逻辑,然后再添加USB通信部分。它们被移动成为基于套接字的东西,然后从使用原始套接字转移到使用Python SocketServer模块。每当事情发生变化时,我发现我必须重写相当多的部分测试,这很烦人。我认为在我的开发过程中测试将是一个有点不变的指导。他们感觉更像是要处理的代码。
  3. 我需要一个客户端和一台服务器通过该通道进行通信以测试任何一方。我可以嘲笑其中一方来测试另一方但是整个频道都不会被测试,我担心我会错过它。这减少了整个红/绿/重构节奏。这只是缺乏经验还是我做错了什么?
  4. “假它直到你制造它”给我留下了很多混乱的代码,后来我花了很多时间来重构和清理。这是事情的运作方式吗?
  5. 在会话结束时,我现在让我的客户端和服务器运行大约3或4个单元测试。我花了大约一个星期的时间来做这件事。如果我在代码方式之后使用单元测试,我想我可以在一天内完成它。我没有看到收益。
  6. 我正在寻找那些使用这种方法完全(或几乎完全)实施大型非平凡项目的人的意见和建议。我有理由按照之后的方式我已经运行了一些东西,并且想要添加一个新功能但是从头做起它似乎很烦人而且不值得努力。

    P.S。 :如果这应该是社区维基,请告诉我,我会这样标记。

    更新0 :所有答案都同样有用。我选了一个我做的,因为它最能引起我的经历。

    更新1 :练习练习!

7 个答案:

答案 0 :(得分:10)

作为初步评论,TDD需要练习。当我回顾我开始TDD时所写的测试时,我看到很多问题,就像我看几年前编写的代码一样。继续这样做,就像你开始认识到糟糕的好代码一样,你的测试会发生同样的事情 - 耐心等待。

  

你如何制作这样的清单?一世   最初有一些像“服务器”的项目   应该启动“,”服务器应该中止   如果频道不可用“等等   他们混在一起,最后,现在,它是   就像“客户应该是这样的   能够连接到服务器“

“列表”可以是非正式的(Beck的书就是这种情况)但是当你开始将项目变成测试时,尝试将语句写成“[当发生这种情况时]”然后[这个条件应该这是真的“格式。这将迫使您更多地考虑您正在验证的内容,如何验证它以及如何直接转换为测试 - 或者如果不是,它应该为您提供关于哪些功能缺失的线索。想想用例/场景。例如,“服务器应该启动”不清楚,因为没有人正在启动一个动作。

  

每次事情发生变化,我都发现了   我不得不重写相当多的部分   那些烦人的测试。 ID   认为测试将是一个   在我的期间有点不变的指导   发展。他们感觉更像   要处理的代码。

首先,是的,测试是更多的代码,需要维护 - 编写可维护的测试需要练习。我同意S. Lott,如果你需要经常更改你的测试,你可能会测试“太深”。理想情况下,您希望在公共接口级别进行测试,这种级别不太可能发生变化,而不是在可能发展的实现细节级别进行测试。但是部分练习是关于设计的,所以你应该期待它的一些错误并且必须移动/重构你的测试。

  

我可以嘲笑其中一方进行测试   另一个但整个频道   不会被测试,我担心   我想念那个。

不完全确定那一个。从它的声音来看,使用模拟是正确的想法:采取一方,模拟另一方,并检查每一方是否正常工作,假设另一方正确实施。整个系统的测试是集成测试,您也希望这样做,但通常不是TDD过程的一部分。

  

“假它直到你成功”离开了我   我后来有很多杂乱的代码   花了很多时间重构和   清理。这是事情的运作方式吗?

在进行TDD时,您应该花费大量时间进行重构。另一方面,当你伪造它时,它是暂时的,你的下一步应该是取消伪造它。通常你不应该通过多次测试,因为你假装它 - 你应该一次专注于一件,并尽快重构它。

  

我想我可以在一天内完成它   如果我之后使用单元测试   代码方式。我没有看到收益。

同样,它需要练习,你应该随着时间的推移变得更快。此外,有时TDD比其他TDD更富有成效,我发现在某些情况下,当我确切地知道我想编写的代码时,编写代码的大部分代码然后编写测试会更快。 除了贝克,我喜欢的一本书是Roy Osherove的单元测试艺术。它不是TDD书籍,它是面向.Net的,但你可能想要看一看:很好的部分是关于如何编写可维护的测试,测试质量和相关问题。我发现这本书在经过书面测试后很有共鸣,有时也很难做到...... 所以我的建议是,不要过快地扔掉毛巾,并给它一些时间。您可能还想更轻松地尝试一下 - 测试服务器通信相关的事情听起来不是最容易开始的项目!

答案 1 :(得分:8)

  
      
  1. Kent Beck使用列表......最后,它就像“客户端应该能够连接到服务器”(包含服务器启动等)。
  2.   

通常是一种不好的做法。

对架构的每个单独层进行单独测试是好的。

综合测试往往会掩盖架构问题。

但是,只测试公共功能。不是每个功能。

不要投入大量时间来优化测试。测试中的冗余不会像在工作应用程序中那样受到伤害。如果事情发生变化并且一个测试工作,但另一个测试中断,那么也许你可以重构你的测试。不是之前。

  

2。你如何处理重写? ......我发现我必须重写大部分测试。

您的测试结果太低了。测试最外层的公共可见界面。那应该是不变的部分。

是的,重大的架构变化意味着重大的测试变更。

测试代码是您证明工作的方式。它几乎与应用程序本身一样重要。是的,这是更多的代码。是的,你必须管理它。

  

3。我需要一个客户端和一个服务器通过该通道进行通信以测试任何一方。我可以嘲笑其中一方来测试另一方但是整个通道都不会被测试......

有单元测试。嘲笑。

有集成测试,可以测试整个测试。

不要混淆他们。

您可以使用单元测试工具进行集成测试,但它们是不同的东西。

你需要做两件事。

  

4。 “伪造它直到你制造它”给我留下了许多混乱的代码,我后来花了很多时间来重构和清理。这是事情的运作方式吗?

是。这正是它的工作原理。从长远来看,有些人发现这比试图预先做好所有设计的大脑更有效。有些人不喜欢这样,想要预先做好所有设计;如果你愿意的话,你可以自由地做很多设计。

我发现重构是一件好事,预先设计太难了。也许是因为我已经编码了近40年而且我的大脑已经疲惫不堪了。

  

5。我没有看到收益。

所有真正的天才都发现测试会减慢它们的速度。

我们其他人不能确定我们的代码有效,直到我们有一套完整的测试证明它的工作原理。

如果您不需要证明您的代码有效,则无需进行测试。

答案 2 :(得分:3)

  

Q值。 Kent Beck使用他添加的列表并从中指出开发过程。你怎么做这样的清单?我最初有一些项目,如“服务器应该启动”,“服务器应该中止,如果渠道不可用”等但他们混合,最后现在,它只是“客户端应该能够连接到服务器”(其中包含服务器启动等。)。

我从挑选任何我可能检查的东西开始。在您的示例中,您选择了“服务器启动”。

Server starts

现在我想找一些我想写的更简单的测试。变化较小,运动部件较少的东西。例如,我可能会考虑“正确配置服务器”。

Configured server correctly
Server starts

但实际上,“服务器启动”取决于“正确配置服务器”,因此我将该链接清除。

Configured server correctly
Server starts if configured correctly

现在我寻找变化。我问,“会出现什么问题?”我可以错误地配置服务器。有多少不同的方式重要?每个人都做了一个测试。即使我正确配置服务器,服务器怎么可能无法启动?每个案例都会进行测试。

  

Q值。你如何处理重写?我最初选择了基于命名管道的半双工系统,这样我就可以在自己的机器上开发应用程序逻辑,然后再添加USB通信部分。它们被移动成为基于套接字的东西,然后从使用原始套接字转移到使用Python SocketServer模块。每当事情发生变化时,我发现我必须重写相当多的部分测试,这很烦人。我认为在我的开发过程中测试将是一个有点不变的指导。他们只是想要处理更多代码。

当我改变行为时,我觉得改变测试是合理的,甚至先改变它们!如果我必须更改不直接检查我正在改变的行为的测试,那么这表明我的测试依赖于太多不同的行为。那些是集成测试,我认为这是一个骗局。 (谷歌“整合测试是骗局”)

  

Q值。我需要一个客户端和一个服务器通过该通道进行通信以测试任何一方。我可以嘲笑其中一方来测试另一方但是整个频道都不会被测试,我担心我会错过它。这减少了整个红/绿/重构节奏。这只是缺乏经验还是我做错了什么?

如果我构建了一个客户端,一个服务器和一个通道,那么我会尝试单独检查每个客户端。我从客户端开始,当我测试它时,我决定服务器和通道如何表现。然后我实现每个通道和服务器以匹配我需要的行为。检查客户端时,我存根通道;检查服务器时,我嘲笑频道;检查通道时,我存根并模拟客户端和服务器。我希望这对你有意义,因为我必须对这个客户端,服务器和频道的性质做一些严肃的假设。

  

Q值。 “伪造它直到你制造它”给我留下了许多混乱的代码,我后来花了很多时间来重构和清理。这是事情的运作方式吗?

如果你在清理它之前让你的“伪造”代码变得非常混乱,那么你可能花了太长时间伪装它。也就是说,我发现即使我最终用TDD清理了更多的代码,整体节奏感觉要好得多。这来自练习。

  

Q值。在会话结束时,我现在让我的客户端和服务器运行大约3或4个单元测试。我花了大约一个星期的时间来做这件事。如果我在代码方式之后使用单元测试,我想我可以在一天内完成它。我没有看到收益。

我必须说,除非你的客户端和服务器非常非常简单,否则你需要进行3到4次以上的测试才能彻底检查它们。我猜你的测试会同时检查(或至少执行)许多不同的行为,这可能会说明你编写它们所花费的时间。

另外,不要测量学习曲线。我的第一次真正的TDD体验包括在9,14小时内重写3个月的工作。我进行了125次测试,耗时12分钟。我不知道我在做什么,感觉很慢,但感觉很稳定,结果很棒。我基本上在3个星期内重写了最初需要3个月才出错的内容。如果我现在写的,我可能会在3-5天内完成。区别?我的测试套件将进行500次测试,运行时间为1-2秒。这就是练习。

答案 3 :(得分:2)

作为一名新手程序员,我发现测试驱动开发的棘手问题是测试应该首先出现。

对于新手来说,事实并非如此。设计是第一位的。 (接口,对象和类,方法,以及适合您的语言的任何内容。)然后,您将测试写入其中。然后你编写实际做的东西的代码。

我看了这本书已经有一段时间了,但是贝克似乎写道,好像代码的设计只是在脑海中无意识地发生。对于有经验的程序员来说,这可能是真的,但对于像我这样的新手,nuh-uh。

我发现Code Complete的前几章对于设计思考非常有用。他们强调这样一个事实,即你的设计可能会发生很大的变化,即使你在实施的细节上也是如此。当发生这种情况时,您可能不得不重新编写测试,因为它们基于与您的设计相同的假设。

编码很难。我们去购物吧。

答案 4 :(得分:1)

对于第一点,请参阅question我问了一会儿与你的第一点有关。

我不会依次处理其他问题,而是提供一些全球性的建议。实践。我花了很长时间和一些'狡猾的'项目(个人虽然)实际获得TDD。只是为了更加令人信服的原因,为什么TDD如此优秀。

尽管测试驱动了我的代码设计,但我仍然得到了一个白板并且编写了一些设计。从这一点来说,至少你已经了解了自己的目标。然后我生成我认为需要的每个夹具的测试列表。一旦开始工作,更多功能和测试将添加到列表中。

从您的问题中脱颖而出的一件事是再次重写您的测试的行为。这听起来像是在进行行为测试,而不是状态。换句话说,测试听起来与您的代码密切相关。因此,一个不影响输出的简单改变将打破一些测试。单元测试(至少是良好的单元测试)也是一项掌握的技能。

我非常推荐Google Testing Blog,因为那里的一些文章让我对TDD项目的测试更好。

答案 5 :(得分:1)

命名管道放在正确的接口后面,改变接口的实现方式(从命名管道到套接字到另一个套接字库)应该只影响实现该接口的组件的测试。因此,更多/不同地削减内容会有所帮助......套接字后面的接口可能会演变为。

我6个月前开始做TDD?我还在学习自己。我可以说随着时间的推移,我的测试和代码变得更好,所以继续保持下去。我也非常推荐XUnit Design Patterns这本书。

答案 6 :(得分:1)

  

如何制作要添加的列表   并从中引导出来   发展过程?我最初有一个   像“服务器应该启动”这样的项目   up“,”服务器应该在通道中止   不可用“

TDD TODO列表中的项目比这更精细,它们的目的只是测试一种方法的一种行为,例如:

  • 测试成功的客户端连接
  • 测试客户端连接错误类型1
  • 测试客户端连接错误类型2
  • 测试成功的客户沟通
  • 未连接时测试客户端通信失败

您可以为您提供的每个示例构建一个测试列表(正面和负面)。此外,在单元测试时,您不在服务器和客户端之间建立任何连接。你只是孤立地调用方法,...这回答了问题3.

  

你如何处理重写?

如果单元测试测试行为而不是实现,则不必重写它们。如果单元测试代码确实创建了一个与生产代码进行通信的命名管道,那么显然在从管道切换到套接字时必须修改测试。 单元测试应远离文件系统,网络,数据库等外部资源,因为它们很慢,可能不可用......请参阅这些Unit Testing rules

这意味着最低级别的功能未经过单元测试,它们将通过集成测试进行测试,整个系统将进行端到端测试。