TDD仅在理论上


29

一年多以前,我很幸运能够休息9个月。我决定在那段时间里磨练自己的C#技能。我开始从事许多项目,并强迫自己遵循TDD。

这是一个相当启发的过程。

刚开始时很难,但是随着时间的流逝,我学会了如何编写更多可测试的代码(事实证明,这往往是更多的SOLID代码),并且在此过程中,我还提高了OO设计技巧。

现在,我回到了工作岗位,并且注意到了一些奇怪的事情。

我宁愿不遵循TDD。

我发现TDD会使我慢下来,实际上使设计干净的应用程序更加困难。

相反,我采用了一种(完全)不同的方法:

  1. 挑选垂直的作品
  2. 开发功能正常的原型
  3. 重构直到一切都变得整洁
  4. 请欣赏一下我编写的精美的SOLID和可测试的代码。

您可能已经注意到,第1步不是“定义我的测试目标的公开表面”,而第2步不是“在所述公开表面上测试耶稣”。您可能还注意到,所有步骤都不涉及测试。我正在编写可测试的代码,但是我尚未对其进行测试。

现在,我想说明一下,我实际上并没有进行任何类型的测试。我正在编写的代码有效。它有效,因为我正在手动测试它。

我还想明确指出,我也没有提到所有自动化测试。这是我的过程与众不同的地方。这就是为什么我问这个问题。

TDD理论上。不在实践中。

我的过程有所发展,我在TDD和没有发现我认为非常有效且也相当安全的测试之间取得了平衡。内容如下:

  1. 在考虑到测试的情况下实现工作的垂直工作,但是不要编写任何测试。
  2. 如果在路上(例如,一个月后),该切片需要修改
    1. 编写单元测试,集成测试,行为测试等,以确保工作片段正确无误
    2. 修改代码
  3. 如果该切片不需要修改,
    1. 没做什么

通过简单地将编写测试的负担从编写代码之前转移到修改代码之前,我已经能够生产出更多的工作代码。而且,当我确实着手编写测试时,我编写的测试数量要少得多,但是覆盖的范围却差不多(投资回报率更高)。

我喜欢这个过程,但是我担心它可能无法很好地扩展。它的成功取决于开发人员在更改测试之前勤于编写测试。这似乎是一个很大的风险。但是,TDD具有相同的风险。

那么,我该死[BT] DD,还是这是一种实用的编码和测试的常见形式?

我想继续这样工作。从长远来看,我该怎么做才能使该过程正常运行?

注意:

我是项目的唯一开发人员,我负责所有工作:需求收集,设计,架构,测试,部署等。我怀疑这就是我的流程正常运行的原因。


2
看起来像尖峰并稳定,如果没有总是做稳定If that slice doesn't need modificationlizkeogh.com/2012/06/24/beyond-test-driven-development
RubberChickenLeader

13
您会发现我长期以来一直在怀疑的有关TDD的一些东西,即测试优先原则是一种非常好的学习工具,但它不是设计,而是鼓励良好的设计。最后,您需要的是可测试的代码和单元测试,它们可以提供良好的代码覆盖率并反映软件的需求;如您所知如果您遵循明智的设计原则则无需先编写测试就可以实现。
罗伯特·哈维

5
是的,首先编写测试实质上会使原型开发工作加倍。
罗伯特·哈维

3
这意味着我只是在撒谎。
MetaFight 2015年

1
“而且,当我真正着手编写测试时,我编写的代码要少得多,但是覆盖的范围却要大得多(ROI更高)”。当您说编写的代码要少得多时,您的意思是说,您只是在测试代码而已。是否正在更改,或者您是说正在以某种方式比使用TDD覆盖更少的测试覆盖相同(经过测试的)代码?
Ben Aaronson

Answers:


6

为了使该过程长期有效,我将在编写代码时编写测试。

这似乎与您的方法相矛盾。但是,您提出了这个问题,所以我给您我的看法:

您不必在代码之前编写测试。忘记那种纯洁。但是,您希望那时左右编写测试。
一旦您的代码正常工作,您就对其进行了一些微调,发现了一些错误(我们在这里谈论的是小时的时间尺度),然后您就可以对代码的工作有最全面的了解。这是编写捕获您的知识的测试的好时机。

将此保留到以后意味着知识会(自然地)随着时间的流逝而减少。

这也意味着,如果您离开并应该由其他人接管您,则不会因为没有记录(通过测试)做什么而直接承担技术责任。

最重要的是,“某一天”可能不会到来。您可能会被公共汽车撞到,也可能登上公共汽车以进行新的冒险。

最后,手动测试无法扩展,并且经常无法覆盖最终用户使用的所有设备。


我认为我喜欢您建议的方法,并且我会尽可能地应用它。我的工作非常分散,因此“小时数”并不总是可能的。不幸的是,我也得到了支持,所以我经常下班来帮助扑灭大火:)但这就是生命。
MetaFight 2015年

问题是明天永远不会到来,总会有下一个功能。您将要选择做什么?写下一个功能,或者为刚刚完成的事情编写测试?
安迪

9

尽管TDD很难100%实施,但是您的方法存在缺陷

  1. 实施工作的垂直工作

    1.1 1年过去了...

    1.2一名新开发人员开始从事该项目

  2. 如果该切片需要修改

    2.3解析“ Clean Coding”样式方法名称和参数“ GetUnicorn(colourOfUnicorn)”

    2.4阅读xml注释'获得一只金麒麟(用于骑行)(obvs)'

    2.5寻找原始开发者

    2.6希望他们记得代码应该做什么

    2.7让他们解释一切

  3. 编写单元测试,集成测试,行为测试等, 希望可以保证工作是正确的

  4. 修改代码

我认为您可以正确地识别出,当需要修改时,单元测试确实可以显示其价值。


2
嘿,我写自我记录代码!我的课程是一种责任,因此很容易理解。没有人需要追捕我:)
MetaFight 2015年

7
@MetaFight,如果他们这样做,您将很容易在实心的金活独角兽之上发现!
jonrsharpe

3
我称他为Goldicorn。
MetaFight 2015年

更严重的是,是的,您有意思。我考虑过记录“测试债务”的故事,以便我的工作量减轻时可以偿还。
MetaFight 2015年

4
如果代码写得好,但有一个错误,它可能会很容易理解什么原开发商意味着通过阅读代码来完成,而新的开发可以添加必要的测试。唯一的问题是,大多数开发人员认为他们正在编写良好的代码。“良好”在很大程度上取决于您的观点和程序员的经验。因此,必须对此进行管理。
Phil

4

我同意Daniel Hollinrake和Ewan的观点,到目前为止,“仅测试是否修改”效果很好的第一个关键点是:

I am the sole developer on my projects and I am responsible for everything

第二个可能的关键点是:

you're producing nice clean code

我认为TDD不会为唯一的程序员带来巨大的生产力提升,如果您已经在编写优质的代码,则它可能不会极大地提高代码的质量。

但是,TDD肯定会提高劣等/经验不足/过时的程序员的代码质量,尤其是在需要修改代码而又不会破坏其他任何东西的时候。甚至更重要的是,如果修改代码的人与最初编写该代码的人不是同一人,或者几个月之间已经过去了几个月。

换句话说,我认为TDD既是提高代码质量(如您所承认的那样)的良好实践,又是(更重要的是)与普通或中等水平的程序员一起工作时的一种对冲(例如,部门或其他公司),这比单独工作更为普遍。


1
我认为部分问题是可能只有1个程序员,但是代码库通常会随着时间的推移而增长,并且当代码很小时有效的(用于测试)随着代码的增大而无法继续工作。
Michael Durrant 2015年

3

对我来说,关键的事情似乎是这样的:

我是项目的唯一开发人员,我负责所有工作:需求收集,设计,架构,测试,部署等。我怀疑这就是我的流程正常运行的原因。

这对您有效,并且您正在生成漂亮的干净代码(我想是!)。我唯一要说的是,创建一个测试工具,以便其他开发人员可以加入并充满信心进行更改。测试工具还可以确保代码行为的一致性。

我认为您的方法与我的相似。我通常是我的项目的唯一开发人员。我发现对TDD的欣赏使我能够编写更小的函数和更简洁的代码,但是我在添加代码的同时添加了测试,以作为测试工具。通过这种方式,随着代码的发展和功能的改变,我可以相当自信地进行更改。

编写测试的第二个原因是,我认为它们是文档的一种形式。他们可以解释为什么创建函数的原因。但是在这里,我在考虑更多有关行为驱动开发的问题。


我想说,测试套件将排在第四位,以传递给其他开发人员-需求文档,体系结构图和设计文档对于交流事务比一堆单元测试更为重要。
gbjbaanb 2015年

这是一个公平的观点,但是根据我的经验,我对文档所做的几乎每个项目(如果存在)都已经过时或不完整。
Daniel Hollinrake

1
同样在这里,这就是为什么开发人员应该意识到记录事物而不是以测试形式编写更多代码的重要性!也许我们需要工具来从代码注释和需求单中更好地生成文档(即,不仅是方法签名的漂亮格式)。
gbjbaanb 2015年

根据您的评论,我对答案做了一些修改。谢谢。
Daniel Hollinrake

1
@gbjbaanb如果可以帮助,我希望避免编写需求文档,体系结构图和设计文档。这是因为它们往往很快就会过时。就我而言,我很幸运,因为我以很少的职责管理许多小型应用程序。这使得需求和体系结构文档有点过大。项目规模很小,整体设计清晰明了。但是,我记录的是系统如何交互,如何部署它们以及如何监视它们的运行状况。
MetaFight 2015年

3

单元测试是关于解决代码维护问题的。尽管有些人说使用TDD而不是不使用TDD可以更快地编写代码,但是您无需编写测试就能编写更多新代码,我并不感到惊讶。

在您更改测试之前,我在编写测试的实践中可以看到的问题:

我经常需要赶时间做出改变

通过仅在需要时编写测试可以节省总体时间,但并非所有时间都是平等的。花2个小时编写测试,在处于危机模式时可以节省1个小时-完全值得。

在编写代码的同时更容易编写测试

要正确编写单元测试,您需要了解我正在测试的代码。我经常将单元测试用作理解的练习,但是对现有代码进行单元测试可能很耗时,因为理解现有代码很耗时。与在编写代码时编写测试相反,由于您已经理解了代码,因此发现它会更快得多,因为您刚刚编写了代码!


Michael Feathers对遗留代码的定义是没有测试的代码。不管您是否同意他的定义,它都清楚地表明,修改现有代码的很大一部分成本是确保其仍按预期运行,通常甚至不知道预期的行为是什么。

编写单元测试通过编码对正确行为的理解以及为“未来我们”检查行为是否正确提供了一种简便的方法,从而抵消了成本损失。


2

这是一个很好的问题,FWIW我将投入两分钱。

大约一年前,我在Salesforce中进行编码,该平台具有一种根深蒂固的机制,它迫使您不一定要在编写代码之前编写测试,而是迫使您总体上编写测试。

它的工作方式是系统将迫使您编写测试,并且它将对要测试的代码行数进行一定百分比的计算。如果整个生产实例中的所有代码都低于经过测试的75%。.Salesforce将无法进行更多工作。

最终的结果是,每当您在Salesforce中执行任何操作时,都必须编写或更新测试。尽管我确信这会对Salesforce的市场份额产生巨大影响,但就开发人员的生命而言,这是一个巨大的难题

在很多时候,您只是想获得一张小票,然后进行测试,并使您的开发时间加倍,因为您只知道可以使用的功能。

然后,TDD的笨拙概念席卷了我们的部门,直达我们的数据库。我们的架构师希望对IT部门的各个方面进行全面的测试。屁股上有轻微的疼痛,甚至遇到更大的疼痛。

那时TDD对我而言从来没有真正意义,甚至现在也没有。我在当前角色中编写的许多功能都是通过与您所提到的机制类似的方式实现的:在垂直切片中进行细化,直到它们起作用为止。当我担任旧职位时,直到现在我仍然经常不知道自己的代码将要做什么,直到我真正编写它,所以我可以编写测试来驱动我将要编写的代码。对我来说没有意义,很麻烦,而且主要是在浪费时间。

综上所述,测试是奇妙而神奇的事情,它们使世界上的一切都变得正确。它们使您的代码正确无误,确保您的应用程序执行了您认为的工作,并且通常情况下一切都变得更加流畅。然后,问题不在于您是在编写代码之前还是在编写代码之后编写测试,而是要花多少时间进行测试。至少在我的软件开发经验中,这是真正的问题。测试需要时间和金钱,您必须在利益冲突的框架内进行测试。

因此,总的来说,我同意您的观点:TDD在实践中有点笨拙且麻烦。在这一点上,您需要记住在当前情况下最有效的方法。如果您要编写关键代码,请确保已对其进行了总体测试。如果有时间,请尝试一下TDD,看看它是否对过程有所帮助。


2
我觉得我们距离正确的TDD大约只有一种语言。我认为,现在使用xUnit框架对大多数语言来说,TDD都是“固定的”。在某些时候,它将仅内置于编码的完成方式中-而不是分开进行。就像您定义一个类一样,将立即生成所有测试的存根,以及测试本身的一些子集(可以轻松地由类/方法本身确定的子集)。
Calphool

3
@Calphool我们已经真正集成了一些容易测试的东西到语言!我们称其为静态类型。Rust通过借阅检查进一步测试了更多的错误。但是大多数测试都是针对该确切的类的(“如果我单击按钮,小部件会变成红色”)-编译器/ IDE怎么可能知道您要进行测试?
user253751

1
@immibis:也许通过进一步扩展类型检查。也许“窗口小部件变成红色”的概念可能成为一流的概念,可以从代码中以某种方式推论得出。我没有声称有答案,我只是觉得TDD还很新,还没有完全集成到语言发展中。
Calphool

1
Salesforce特别将测试全部错了:他们要求必须有适当的测试,但是很难编写质量测试。从理论上讲,这听起来不错,但实际上,它使开发人员想要用勺子挖洞。

1

我不能推荐您的方法。

如果我使用您的方法,例如将如下所示(房子就是应用程序):

  1. 我开始为我的家人盖房子,作为有一定知识的瓦工或初学者。
  2. 我知道诸如儿童房,客房之类的要求,并开始建造我的“原型”房屋。
  3. 数次之后,您的“原型”房子就完成了。
  4. 我开始手动查看结构是否足够稳定。因此,我承担了很多重担,并将其带入一楼的不同房间。为了确保当我和家人一起坐在房间里时,天花板不会破裂。但是它坏了,我开始重构。首先清理所有的东西。比新建它,然后再次手动测试它,直到足够稳定为止。
  5. 比起和家人住在一起。一切皆好。
  6. 不久以后,我的堂兄和父母们就来拜访我们。但是在他们进入我们的房子之前,他们需要付钱给建筑师和土木工程师,以确保当我们坐在二楼的一个房间中时,天花板不会破裂。
  7. 建筑师和土木工程师的工作量很大,因为他们一无所有。因此,他们需要进入我的房子,看看我是如何建造的。
  8. 再次,它不够稳定。因此,他们必须重构一楼的地面。
  9. 但是之后一切都很好,所有人都可以安全进入我的房子。

因此,在我用您的方法建造房屋之前,您的方法需要花费大量时间和知识。否则会花费很多时间!同样,当需求发生变化时,让其他人为您的代码编写测试也不是绅士。

因此,与开始重构相比,没有编程“原型”并且有更好的方法。而不是编写原型”,请按照以下说明使用应用程序的UML进行设计。

  1. 创建一个用例图。您可以使用draw.io开始。
  2. 比根据您的用例创建一个EPK图来确定行为。(您的应用程序的行为)重构比重构编码的原型要快。尤其是当您是初学者时。
  3. 创建一个类图。(您的应用程序的结构)
  4. 确定在行为实施中可能遇到麻烦的地方。
  5. 为此编写一个可能包含10或20行代码的简单原型,以确定如何实现此行为。适合初学者。或观看教程,查看其他示例应用程序的源代码。他们是如何解决的。
  6. 比开始编码。击打UseCase的成功测试。这可以通过不同的方式来完成。首先创建测试和该UseCase所需的所有结构。使用Enterprise Architekt时,可以为您生成结构。根据您的图表。或在连接测试时创建结构。因此,不会出现编译错误。这里提到的是,您只需要测试应用程序的行为即可。您拥有的用例。
  7. 比实现UseCase的行为。
  8. 成功使用案例之后,开始为异常编写测试。当测试有效时,当您看到绿色时,感觉总是很好;)
  9. 您完成了。

当然,这种方法还需要UML方面的一些知识,但是学习起来很快。与在IDE中进行重命名相比,重命名Class或移动图中的箭头总是更快。但是,从一开始就学习测试框架的使用将更加耗费精力。最好的办法是对开源项目进行运行测试,并研究它们的工作方式。但是,当您拥有一个测试驱动的应用程序时,下一个应用程序将更快。我想知道一切正常,这是一种很好的感觉。

所以我不赞成这种方法,因为它们对于初学者来说非常耗时,而且毕竟不好。要在结构和行为之间建立清晰的边界,可以使用域驱动的设计,并在“非常域”下安排两个包(一个包命名为“结构”,另一个命名为“行为”)。也用于您的测试。简单的示例请查看用Java编写的本示例


1

我正在编写的代码有效。它有效,因为我正在手动测试它。

更改后,您是否手动测试了条件的每个可能分支?您的手动测试的反馈回路需要多长时间。它与自动测试所获得的反馈循环有多接近。

自动化测试(无论测试优先与否)都可以使您更快-通过在代码上提供更快的反馈循环。

您确定您会记得六个月后手动测试某些条件吗?请不要说您会记录所有要测试的重要条件-因为编写某种文档/注释等于编写test(可执行文档)

  • 挑选垂直的作品

  • 开发功能正常的原型

  • 重构直到一切都变得整洁

再说一遍:重构时,您是否手动测试了所有受重构影响的逻辑?测试重构变更需要花费多长时间?如果重构中断某些代码,您需要花费多长时间才能找到中断的原因?

  • 请欣赏一下我编写的精美的SOLID和可测试的代码。

您喜欢的漂亮干净的代码非常主观。您的代码对您而言可能是干净且合理的。检查您的代码是否真正可读,可理解和可测试的最佳方法是其他开发人员进行的测试和代码审查。

您发现自己的方式非常有效率,这是因为您只是从事代码工作的开发人员,而且,我认为,因为您仅开始从事该项目(您从事该项目的年龄?6-8个月?)。
您仍然记得自己编写的所有内容,并且可以识别出可能出现问题的原因。我很确定您将在项目的2-3年后开始编写测试,因为您要确保自己不会忘记任何事情。


0

如果您从未犯过错误,那么您实际上不需要测试。大多数开发人员都会犯错误,但是如果您从不犯错,并且您有信心将来不会犯错误(并且您是项目中唯一的人),那么实际上没有理由浪费时间编写测试。

但是您的解决方案只是半途而废,因为您打算在更改代码时编写测试,但是与此同时,您的方法假定您在决定为测试编写代码的那一部分时就不会犯错。仅当您始终完全了解更改可能影响哪些区域时,此方法才有效。我认为许多普通开发人员(当然不是您!)经历过更改,然后在意外地方进行的测试失败了,因为您犯了一个错误。

当然,良好的体系结构,SOLID原理等都可以防止这种情况的发生,但是大多数开发人员并不完美,这就是为什么对整个系统进行测试很有价值的原因。


当然,良好的体系结构,SOLID原理等都可以防止这种情况的发生 -不。复杂系统的某些部分会影响其他部分,事实就是如此。例如,在Rubberduck中修改Antlr语法可以轻松使预期的修改零件完美地工作,同时破坏其他45个功能。没有全面的测试,就无法知道,并且每次都要手动测试所有案例时,您将很疯狂。如果我在解析器中更改了某些内容,但987测试失败了,我知道我做错了什么并且影响了什么。
Mathieu Guindon
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.