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

我开始了,很快就陷入了一堆混乱的重写和小测试中,后来我发现这些测试并没有真正测试任何东西。我扔掉了其中的大部分,现在有了一个可以运行的应用程序,所有测试都浓缩为 2 个。

根据我的经验,我有几个问题想请教一下。我从中获得了一些信息 TDD 新手:是否有带有测试的示例应用程序来展示如何进行 TDD? 但有一些具体问题我希望得到解答/讨论。

  1. Kent Beck 使用一个他添加和删除的列表来指导开发过程。你如何列出这样的清单?我最初有一些项目,例如“服务器应该启动”,“如果通道不可用,服务器应该中止”等。但它们混合在一起,最后现在,它只是类似于“客户端应该能够连接到服务器”(其中包含服务器启动等)。
  2. 你如何处理重写?我最初选择了基于命名管道的半双工系统,以便我可以在自己的机器上开发应用程序逻辑,然后添加USB通信部分。他们转变为基于套接字的东西,然后从使用原始套接字转变为使用 Python SocketServer 模块。每次事情发生变化时,我发现我必须重写相当一部分的测试,这很烦人。我认为测试在我的开发过程中将是一个不变的指南。他们只是觉得需要处理更多代码。
  3. 我需要一个客户端和一个服务器通过通道进行通信以测试任一端。我可以模拟一侧来测试另一侧,但是整个通道不会被测试,我担心我会错过这一点。这破坏了整个红/绿/重构的节奏。这只是缺乏经验还是我做错了什么?
  4. “假装直到成功”给我留下了很多混乱的代码,后来我花了很多时间来重构和清理它们。事情就是这样吗?
  5. 在会话结束时,我现在让我的客户端和服务器运行大约 3 或 4 个单元测试。我花了大约一周的时间才完成。我想如果我在代码之后使用单元测试,我可以在一天内完成。我看不到收获。

我正在寻找完全(或几乎完全)使用这种方法实施大型非平凡项目的人们的评论和建议。遵循这条路对我来说是有意义的 我已经有一些东西正在运行,并且想要添加一个新功能,但从头开始似乎很烦人,而且不值得付出努力。

附::请告诉我这是否应该是社区维基,我会这样标记它。

更新0 :所有的答案都同样有帮助。我选择了我所做的一个,因为它与我的经历最有共鸣。

更新1:练习练习练习!

有帮助吗?

解决方案

作为初步评论,TDD 需要实践。当我回顾开始 TDD 时编写的测试时,我发现了很多问题,就像我查看几年前编写的代码一样。继续这样做,就像您开始识别好代码和坏代码一样,您的测试也会发生同样的事情 - 要有耐心。

你如何列出这样的清单?最初,我有一些项目,例如“服务器应该启动”,“如果没有通道,服务器应该中止”等。但是它们混杂在一起,最后,这只是“客户应该能够连接到服务器”之类的东西

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

每次发生变化时,我都会发现我必须重写很烦人的测试部分。我认为测试在我开发期间将是一个有些不变的指南。他们只是觉得要处理更多的代码。

首先,是的,测试是更多的代码,并且需要维护——编写可维护的测试需要练习。我同意S的观点。Lott,如果您需要大量更改测试,那么您可能测试“太深”了。理想情况下,您希望在不太可能改变的公共接口级别进行测试,而不是在可能演变的实现细节级别进行测试。但练习的一部分是提出设计,因此您应该预料到会出现一些错误,并且还必须移动/重构您的测试。

我可以嘲笑一个方面测试另一侧,但是不会对整个频道进行测试,我担心我会想念的。

对此不太确定。从它的声音来看,使用模拟是正确的想法:采取一侧,模拟另一侧,并检查每一侧是否正常工作,假设另一侧已正确实现。一起测试整个系统就是集成测试,您也想这样做,但通常不是 TDD 过程的一部分。

“伪造直到你做到”给我留下了很多混乱的代码,后来我花了很多时间来重构和清理。事情就是这样吗?

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

我认为,如果我在代码之后使用单元测试,我可以在一天之内完成。我看不到收获。

再次强调,这需要练习,随着时间的推移,你应该会变得更快。另外,有时 TDD 比其他方法更富有成果,我发现在某些情况下,当我确切地知道我要编写的代码时,编写一部分代码然后编写测试会更快。
除了 Beck 之外,我喜欢的一本书是 Roy Osherove 写的《单元测试的艺术》。这不是一本 TDD 书,它是面向 .Net 的,但您可能还是想看一下:一个很好的部分是关于如何编写可维护的测试、测试质量和相关问题。我发现这本书与我在进行书面测试后的经历产生了共鸣,有时我很难正确地做到这一点......
所以我的建议是,不要太快认输,给一些时间。您可能还想尝试一些更简单的事情 - 测试服务器通信相关的事情听起来不像是最简单的项目!

其他提示

  1. 肯特·贝克使用一个列表......最后,它只是类似于“客户端应该能够连接到服务器”(其中包含服务器启动等)。

通常是一种不好的做法。

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

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

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

并且不要投入大量时间来优化测试。测试中的冗余不会像工作应用程序中那样造成太大影响。如果情况发生变化并且一个测试有效,但另一个测试失败,也许您可​​以重构您的测试。以前没有。

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

您的测试细节水平太低。测试最外面的、公共的、可见的接口。应该不变的部分。

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

测试代码就是你如何 证明 事情有效。它几乎与应用程序本身一样重要。是的,代码更多。是的,你必须管理它。

3.我需要一个客户端和一个服务器通过通道进行通信以测试任一端。我可以模拟一侧来测试另一侧,但随后整个通道将不会被测试......

有单元测试。带有嘲笑。

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

不要混淆他们。

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

你需要两者都做。

4.“假装直到成功”给我留下了很多混乱的代码,后来我花了很多时间来重构和清理它们。事情就是这样吗?

是的。这就是它的工作原理。从长远来看,有些人发现这比绞尽脑汁试图预先完成所有设计更有效。有些人不喜欢这样,想要预先完成所有设计;如果您愿意,您可以自由地预先进行大量设计。

我发现重构是一件好事,但预先设计太难了。也许是因为我已经编码了近 40 年,我的大脑已经磨损了。

5.我看不到收获。

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

我们其他人不能 当然 我们的代码可以工作,直到我们有一套完整的测试 证明 它有效。

如果你不需要 证明 你的代码可以工作,你不需要测试。

问。Kent Beck 使用一个他添加和删除的列表来指导开发过程。你如何列出这样的清单?我最初有一些项目,例如“服务器应该启动”,“如果通道不可用,服务器应该中止”等。但它们混合在一起,最后现在,它只是类似于“客户端应该能够连接到服务器”(其中包含服务器启动等)。

我首先选择我可能检查的任何内容。在您的示例中,您选择了“服务器启动”。

Server starts

现在我寻找我可能想要编写的任何更简单的测试。变化较少、活动部件较少的东西。例如,我可能会考虑“正确配置服务器”。

Configured server correctly
Server starts

不过,实际上,“服务器启动”取决于“正确配置的服务器”,所以我明确了该链接。

Configured server correctly
Server starts if configured correctly

现在我寻找变化。我问:“怎么了?”我可以错误地配置服务器。有多少种不同的方式很重要?其中每一个都进行测试。即使我配置正确,服务器仍然无法启动,这是怎么回事?每个案例都需要进行测试。

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

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

问。我需要一个客户端和一个服务器通过通道进行通信以测试任一端。我可以模拟一侧来测试另一侧,但是整个通道不会被测试,我担心我会错过这一点。这破坏了整个红/绿/重构的节奏。这只是缺乏经验还是我做错了什么?

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

问。“假装直到成功”给我留下了很多混乱的代码,后来我花了很多时间来重构和清理它们。事情就是这样吗?

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

问。在会话结束时,我现在让我的客户端和服务器运行大约 3 或 4 个单元测试。我花了大约一周的时间才完成。我想如果我在代码之后使用单元测试,我可以在一天内完成。我看不到收获。

我不得不说,除非你的客户端和服务器非常非常简单,否则你需要分别进行超过 3 或 4 次测试才能彻底检查它们。我猜测您的测试会同时检查(或至少执行)许多不同的行为,这可能会解释您编写它们所花费的精力。

另外,不要测量学习曲线。我的第一次真正的 TDD 体验是在每天 9 到 14 小时内重写 3 个月的工作成果。我进行了 125 个测试,运行时间为 12 分钟。我不知道自己在做什么,感觉很慢,但感觉很稳定,而且结果很棒。我基本上在三周内重写了原本花了三个月才出错的内容。如果我现在写的话,大概3-5天就能写完。区别?我的测试套件将包含 500 个测试,运行时间为 1-2 秒。这是通过练习而来的。

作为一名新手程序员,我发现测试驱动开发的棘手之处在于测试应该放在第一位。

对于新手来说,事实并非如此。设计是第一位的。(接口、对象和类、方法,任何适合您的语言的内容。)然后您可以为此编写测试。然后你编写实际执行操作的代码。

我已经有一段时间没有看这本书了,但贝克写的似乎代码的设计就好像在你的脑海中无意识地发生一样。对于经验丰富的程序员来说,这可能是正确的,但对于像我这样的菜鸟来说,嗯嗯。

我找到了前几章 代码完成 对于思考设计非常有用。他们强调这样一个事实,即你的设计很可能会改变,即使你已经陷入了具体的实施层面。当发生这种情况时,您很可能必须重新编写测试,因为它们基于与您的设计相同的假设。

编码很难。一起去购物吧。

对于第一点,请参阅 问题 我不久前问过有关你的第一点的问题。

我不会依次处理其他问题,而是提供一些全局建议。实践。我花了很长时间和一些“狡猾”的项目(尽管是个人的)才真正获得 TDD。谷歌一下就能找到更多令人信服的理由来解释为什么 TDD 如此优秀。

尽管测试驱动了我的代码设计,但我仍然拿到白板并潦草地写下一些设计。由此,至少您对自己应该做什么有一些了解。然后我生成我认为需要的每个夹具的测试列表。一旦您开始工作,更多功能和测试就会添加到列表中。

从您的问题中脱颖而出的一件事是再次重写测试的行为。这听起来像是你在进行行为测试,而不是状态测试。换句话说,测试听起来与您的代码联系得太紧密了。因此,不影响输出的简单更改将破坏某些测试。单元测试(至少是良好的单元测试)也是一项需要掌握的技能。

我推荐 谷歌测试博客 相当重要,因为那里的一些文章使我对 TDD 项目的测试变得更好。

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

我大概 6 个月前开始做 TDD?我自己还在学习中。我可以说,随着时间的推移,我的测试和代码已经变得更好了,所以继续保持吧。我也强烈推荐《XUnit 设计模式》这本书。

您如何使这样的列表添加并删除以指导开发过程?我最初有一些项目,例如“服务器应该启动”,“如果没有频道,则应中止服务器”

TDD TODO 列表中的项目比这更细粒度,它们旨在仅测试一种方法的一种行为,例如:

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

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

你如何处理重写?

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top