【问题标题】:Should unit tests be written before the code is written?是否应该在编写代码之前编写单元测试?
【发布时间】:2008-10-29 14:49:28
【问题描述】:

我知道测试驱动开发的定义原则之一是先编写单元测试,然后编写代码以通过这些单元测试,但有必要这样做吗?

我发现在编写测试之前我常常不知道自己在测试什么,这主要是因为我过去从事的几个项目更多地是从概念验证演变而来的,而不是经过设计的。

我之前尝试过编写单元测试,它可能很有用,但对我来说似乎并不自然。

【问题讨论】:

  • 请创建这个社区维基,因为它不仅仅是投票而不是真正的问题。
  • 这应该是一个真正的问题,而不是另一个民意调查。我希望奥马尔稍微改写一下这个问题,使其成为一个正确的问题,而不是一个民意调查类型的问题。
  • 我很高兴改写我不知道改写成什么。
  • 类似“什么时候是编写单元测试的最佳时间,在你开始编码之前,还是在你完成之后?”怎么样?
  • 我很想知道每个人用来编写单元测试的方法。在编写测试之前,他们是否尝试全面了解问题?还是他们根据当前的理解编写测试,如果理解发生变化,以后再更改这些测试?

标签: unit-testing tdd


【解决方案1】:

这里有一些不错的 cmets,但我认为有一件事被忽略了。

编写测试首先驱动您的设计。这是重要的一步。如果您“同时”或“不久之后”编写测试,您可能会错过以微步骤执行 TDD 的一些设计优势。

一开始感觉真的很俗气,但是看到事情在您眼前展开成您最初没有想到的设计,真是太棒了。我已经看到了。

TDD 很难,并不适合所有人。但是,如果您已经接受了单元测试,那么请试用一个月,看看它对您的设计和生产力有何影响。

您花在调试器上的时间更少,而花更多时间思考由外而内的设计。这是我书中的两个巨大优势。

【讨论】:

  • 如何先创建单元测试? IDE 会弹出很多警告和错误,并且智能不起作用!我不知道该怎么做。
  • 让IDE生成方法存根。一开始所有的测试都会失败。
  • @Pokus - 快速回答是......我关闭了自动语句完成功能,这样智能感知就不会妨碍我。如果我需要它,CTRL+空格会一直显示它。我强烈推荐 ReSharper (jetbrains.com/resharper)。它极大地帮助了我的 TDD 工作流程。
  • 我认为这个想法是您首先编写一个接口并使用(在 C# 中)抛出未实现的异常的存根来实现它。你所有的测试都会失败,编写代码会让测试一个一个通过。
  • @Ben - 您可以从自动完成列表中清除将提交的字符列表;那么只有 TAB 会这样做。两全其美。
【解决方案2】:

studies 表明在编写代码之后编写的单元测试是更好的测试。但需要注意的是,人们不倾向于在事件发生后写它们。因此,TDD 是一个很好的折衷方案,因为至少要编写测试。

因此,如果您在编写代码后编写测试,对您有好处,我建议您坚持下去。

我倾向于发现我做混合。我对需求了解得越多,我可以预先编写的测试就越多。当需求(或我对问题的理解)薄弱时,我倾向于在之后编写测试。

【讨论】:

  • 您能提供这些研究的链接吗?谢谢
  • 此答案中的链接指向质疑研究结果的博客文章,而不是具有不同结果的实际研究。因此,我会打折这个答案第一行的有效性。
  • 该链接指向对研究小组得出的结论的详细分析以及他们的结论错误的原因。研究实际上清楚地表明,后测试优于前测试(只要执行)
【解决方案3】:

TDD 不是关于测试,而是关于测试如何驱动您的代码。 所以基本上你正在编写测试让架构自然发展(不要忘记重构!!!否则你不会从中获得太多好处)。 之后您拥有大量回归测试和可执行文档是一个很好的副作用,但不是 TDD 背后的主要原因。

所以我的投票是: 先测试

PS:不,这并不意味着您不必事先规划您的架构,而是如果测试告诉您这样做,您可能会重新考虑它!!!!

【讨论】:

    【解决方案4】:

    在过去的 6 到 7 年里,我一直领导着开发团队。我可以肯定的是,作为一名开发人员和与我共事过的开发人员,如果我们知道我们的代码适合大局的位置,那么代码的质量就会有显着差异。

    测试驱动开发 (TDD) 帮助我们回答“什么?”在我们回答“如何?”之前它有很大的不同。

    我理解为什么在 PoC 类型的开发/架构师工作中可能会担心不遵循它。你是对的,遵循这个过程可能没有完全的意义。同时,我想强调的是,TDD 是一个属于开发阶段的过程(我知道这听起来已经过时,但你明白了 :) 当低级规范明确时。

    【讨论】:

      【解决方案5】:

      我认为首先编写测试有助于定义代码实际应该做什么。很多时候人们对代码应该做什么或应该如何工作没有一个很好的定义。他们只是开始写作并随着他们的进展而弥补。首先创建测试让您专注于代码将要做什么。

      【讨论】:

        【解决方案6】:

        并非总是如此,但我发现这样做确实有帮助。

        【讨论】:

          【解决方案7】:

          我倾向于在编写代码时编写它们。最多我会在编写之前编写类/模块是否存在的测试。

          我没有提前计划足够多的细节来比要测试的代码更早地编写测试。

          我不知道这是我的想法或方法的缺陷,还是只是 TIMTOWTDI。

          【讨论】:

          • TIMTOWTD?那到底是什么?
          • 有不止一种方法可以做到这一点。代码+测试几乎同时仍然可以是测试驱动的。粗略代码,写好测试,完成代码。
          【解决方案8】:

          我从我想如何调用我的“单元”开始并使其编译。 喜欢:

          picker = Pick.new
          item=picker.pick('a')
          assert item
          

          然后我创建

          class Pick
           def pick(something)
           return nil
           end
          end
          

          然后我继续在我的“测试”用例中使用 Pick,这样我就可以看到我希望如何调用它以及如何处理不同类型的行为。每当我意识到我可能在某些边界或某种错误/异常上遇到问题时,我都会尝试让它触发并获得一个新的测试用例。

          所以,简而言之。是的。 之前做测试的比例比不做的要高很多。

          【讨论】:

            【解决方案9】:

            指令是关于如何采取措施来提高整体质量或生产力甚至最终产品的建议。它们绝不是必须遵守的法律,否则您会被正确的编码实践之神一闪而过。

            这是我对拍摄的妥协,我发现它非常有用且富有成效。

            通常最难搞定的部分是需求,紧随其后的是你的类、API、包的可用性......然后是实际的实现。

            1. 编写您的界面(它们会发生变化,但在了解什么必须做的事情上会有很长的路要走)
            2. 编写一个简单的程序来使用接口(它们是愚蠢的主接口)。这对于确定将要使用的如何大有帮助(根据需要经常回到 1)
            3. 在接口上编写测试(我从 TDD 集成的位,根据需要再次回到 1)
            4. 在接口后面编写实际代码
            5. 对类和实际实现编写测试,使用覆盖工具确保不会忘记奇怪的执行路径

            所以,是的,我在编码之前编写测试,但在我弄清楚需要对特定级别的细节做什么之前从来没有。这些通常是高级测试,仅将整体视为黑匣子。通常会保留为集成测试,一旦接口稳定,不会有太大变化。

            然后我在其背后的实现上编写了一堆测试(单元测试),这些测试会更加详细,并且会随着实现的发展、优化和扩展而经常变化。

            这是严格意义上的 TDD 吗?极端?敏捷...?任何... ?我不知道,坦率地说我不在乎。它适用于。我会根据需要和我对软件开发实践的理解进行调整。

            我的 2 美分

            【讨论】:

              【解决方案10】:

              我已经编程了 20 年,而且我几乎从来没有写过没有运行某种单元测试的代码行——老实说,我知道人们一直都在这样做,但是有人如何可以发布一行没有经过某种测试运行的代码,这超出了我的能力范围。

              如果没有合适的测试框架,我通常会在我编写的每个类中编写一个 main()。它给你的应用程序增加了一点麻烦,但我猜如果有人愿意,他们可以随时删除它(或注释掉它)。我真的希望你的类中只有一个 test() 方法可以自动编译出发布版本——我喜欢我的测试方法和我的代码在同一个文件中......

              所以我已经完成了测试驱动开发和测试开发。我可以告诉你,当你是一个初级程序员时,TDD 真的很有帮助。它可以帮助您学习“从外部”查看代码,这是程序员可以学到的最重要的课程之一。

              TDD 还可以帮助您在遇到困难时继续前进。你可以只写一些你知道你的代码必须做的非常小的部分,然后运行它并修复它——它会让人上瘾。

              另一方面,当您添加到现有代码并且几乎确切地知道您想要什么时,这是一个折腾。您的“其他代码”经常测试您的新代码。您仍然需要确保测试每条路径,但只需从前端运行测试即可获得良好的覆盖率(动态语言除外——对于那些你真的应该对所有内容进行单元测试的语言)。

              顺便说一句,当我参与一个相当大的 Ruby/Rails 项目时,我们的测试覆盖率非常高。我们将一个主要的中心模型类重构为两个类。这本来需要我们两天时间,但经过所有测试,我们不得不重构它,最终接近两周。测试并非完全免费。

              【讨论】:

                【解决方案11】:

                我不确定,但根据您的描述,我感觉可能对 test-first 的实际含义存在误解。它确实意味着您首先编写所有您的测试。这确实意味着你有一个非常紧凑的周期

                1. 编写一个最小的测试
                2. 通过编写必要的最少生产代码使测试通过
                3. 编写下一个将失败的测试
                4. 以最简单的方式更改现有生产代码,使所有现有测试通过
                5. 重构代码(测试和生产!),使其不包含重复并具有表现力
                6. 继续 3. 直到你想不出另一个合理的测试

                一个周期 (3-5) 通常只需几分钟。使用这种技术,您实际上发展设计,同时并行编写测试和生产代码。根本没有涉及太多的前期设计。

                关于它是否“必要”的问题 - 不,显然不是。不做 TDD 成功的项目数不胜数。但是有一些强有力的证据表明,使用 TDD 通常会显着提高质量,而且通常不会对生产力产生负面影响。而且也很有趣!

                哦,关于它感觉不“自然”,这只是你习惯的问题。我知道有些人非常沉迷于每隔几分钟获得一个绿色条(典型的 xUnit 标志,表示“所有测试通过”)。

                【讨论】:

                  【解决方案12】:

                  现在有这么多的答案,他们都是不同的。这与外面的现实完全相似。每个人都在做不同的事情。我认为对单元测试存在巨大的误解。在我看来,好像人们听说过 TDD 并且他们说它很好。然后他们开始编写单元测试,但并没有真正了解 TDD 到底是什么。他们刚刚得到“哦,是的,我们必须编写测试”的部分并且他们同意它。他们也听说过“您应该先编写测试”,但他们并不认真。

                  我认为这是因为他们不了解测试优先的好处,而您只有在这样做一段时间后才能理解。他们似乎总是找到 1.000.000 个借口来解释为什么他们不喜欢先编写测试。因为在弄清楚所有东西如何组合在一起等方面太困难了。在我看来,这都是他们逃避无法自律的借口,尝试测试优先的方法并开始看到好处。

                  如果他们开始争论“我不相信这个测试优先的事情,但我从来没有这样做过”,那是最可笑的事情......太好了......

                  我想知道单元测试最初是从哪里来的。因为如果这个概念真的源自 TDD,那么人们如何理解它是可笑的。

                  【讨论】:

                    【解决方案13】:

                    编写测试首先定义了您的代码的外观 - 即它倾向于使您的代码更加模块化和可测试,因此您不会创建具有非常复杂和重叠功能的“膨胀”方法。这也有助于将所有核心功能隔离在单独的方法中,以便于测试。

                    【讨论】:

                      【解决方案14】:

                      就我个人而言,我认为如果在编写代码之前不进行单元测试,它们的效率就会大大降低。

                      测试的一个古老问题是,无论我们多么努力地思考它,我们永远不会想出每一个可能的场景来编写一个测试来覆盖。

                      显然,单元测试本身并不能完全阻止这一点,因为它是限制性测试,只查看一个代码单元,不涵盖此代码与其他所有代码之间的交互,但它为首先编写干净的代码提供了良好的基础至少应该限制模块之间交互问题的机会。我一直致力于保持代码尽可能简单的原则——事实上,我相信这是 TDD 的关键原则之一。

                      所以从一个测试开始,基本上说你可以创建一个这种类型的类并构建它,理论上,为每一行代码编写一个测试,或者至少覆盖一段特定代码的每条路线。随手设计!显然是基于最初产生的粗略设计,为您提供一个可以工作的框架。

                      正如您所说,一开始是非常不自然的,而且似乎是在浪费时间,但我亲眼目睹了从长远来看,当缺陷统计数据出现并显示完全完成的模块时,它会带来回报随着时间的推移,使用 TDD 编写的缺陷远低于其他方法。

                      【讨论】:

                        【解决方案15】:

                        之前、期间和之后。 之前是规范、合同、工作定义的一部分 期间是在实施过程中发现特殊情况、不良数据、异常的情况。 之后是维护、演进、变更、新需求。

                        【讨论】:

                          【解决方案16】:

                          我不会先编写实际的单元测试,但我会在开始编码之前创建一个测试矩阵,列出所有可能需要测试的场景。我还列出了在对程序的任何部分进行更改时必须测试的案例列表,作为回归测试的一部分,除了全面测试代码位之外,该回归测试将涵盖应用程序中的大多数基本场景改变了。

                          【讨论】:

                            【解决方案17】:

                            请记住,使用 Extreme 编程,您的测试实际上就是您的文档。因此,如果您不知道自己在测试什么,那么您就不知道您希望您的应用程序要做什么?

                            您可以从“故事”开始,可能类似于

                            “用户可以获取问题列表”

                            然后,当您开始编写代码来解决单元测试时。要解决上述问题,您至少需要一个用户和问题类。那么你就可以开始考虑这些领域了:

                            “用户类具有名称 DOB 地址 TelNo 锁定字段”

                            等等。 希望对您有所帮助。

                            狡猾

                            【讨论】:

                              【解决方案18】:

                              是的,如果您使用的是真正的 TDD 原则。否则,只要你在编写单元测试,你就比大多数人做得更好。

                              根据我的经验,在编写代码之前编写测试通常更容易,因为这样做可以为自己提供一个简单的调试工具,以便在编写代码时使用。

                              【讨论】:

                                【解决方案19】:

                                我同时写它们。我为新类和测试类创建骨架代码,然后为某些功能编写测试(然后帮助我了解我希望如何调用新对象),并在代码中实现它。

                                通常,我不会第一次写出优雅的代码,它通常很hacky。但是,一旦所有测试都正常工作,您就可以进行重构,直到最终得到相当整洁、可证明坚如磐石的东西。

                                【讨论】:

                                  【解决方案20】:

                                  当你写一些你习惯写的东西时,它会有所帮助,首先写下你会定期检查的所有东西,然后再写那些功能。更多时候,这些功能对于您正在编写的软件来说不是最重要的。现在,在另一边没有灵丹妙药,事情不应该一成不变。开发人员的判断在决定使用测试驱动开发还是测试后开发时起着重要作用。

                                  【讨论】:

                                    猜你喜欢
                                    • 1970-01-01
                                    • 2010-09-28
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2011-09-06
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 1970-01-01
                                    相关资源
                                    最近更新 更多