Ruby on Rails - 为什么要使用测试?

14

我对Ruby on Rails中各种测试设备的用途感到困惑。 我已经使用这个框架大约6个月了,但我从来没有理解它的测试部分。 我唯一使用过的测试是Java中的JUnit3,而且只用了很短的时间。

我读到的所有关于测试的内容仅仅展示了测试验证。Rails中的验证不应该只是起作用吗?这似乎更像是测试框架而不是测试您的代码。为什么需要测试验证呢?

此外,测试似乎对代码的任何更改都非常脆弱。因此,如果您更改模型中的任何内容,就必须更改测试和固定数据以匹配。这不违反了DRY原则(不要重复自己)吗?

第三,编写测试代码似乎需要很多时间。这正常吗?难道刷新浏览器并查看是否有效不是更快吗?我已经必须测试我的应用程序,以确保其正常流动并确保我的CSS没有出错。为什么手动测试不足够呢?

我曾经问过这些问题,但我得到的回答都只有“自动化测试是自动化的”。我足够聪明,可以理解自动化任务的优点。我的问题是编写测试的成本似乎与获得的好处相比过高。话虽如此,任何详细的回答都受欢迎,因为我可能会错过一两个好处。

6个回答

27
Rails中的验证确实有效--事实上,Rails代码库中有单元测试来确保其有效。当你测试一个模型的验证时,你在测试验证的具体细节:长度、接受的值等。你要确保代码按照预期编写。一些验证是简单的帮助程序,你可以选择不对它们进行测试,因为"没有人会搞砸一个validates_numericality_of调用"。这是真的吗?每个开发人员是否总是记得首先编写它?每个开发人员是否从未因粘贴时出现错误而意外删除了一行?在我个人看来,你不需要测试Rails验证帮助程序的每一个组合,但你需要一行代码来测试它是否存在并传递了正确的值,以防将来某个淘气鬼在没有恰当考虑之前更改它。
此外,其他验证更复杂,需要大量自定义代码,可能需要更彻底的测试。
此外,测试似乎对代码中任何更改都很脆弱。如果你在模型中改变任何东西,你必须改变你的测试和固定装置以匹配。这难道不违反了DRY原则吗?
我不认为这违反了DRY原则。它们传达了(这就是编程,通信)两种非常不同的东西。测试说代码应该做某事。而代码则说明它实际上做了什么。当这两个方面之间存在不一致时,测试非常重要。

测试代码和应用程序代码密切相关,它们就像硬币的两面一样。你不会想要一个只有正面而没有背面的硬币,或者只有背面而没有正面的硬币。良好的测试代码强化了优秀的应用程序代码,反之亦然。它们一起用于理解你试图解决的整个问题。同时,编写良好的测试代码是文档 - 它展示了如何使用应用程序代码。

其次,编写测试代码似乎需要花费很多时间。这很正常吗?难道不直接在浏览器中刷新查看效果更快吗?我已经要在我的应用程序中进行操作以查看是否正确流转,并确保我的 CSS 没有出现问题。为什么手动测试不够呢?

你可能只在非常小的项目上工作过,对于这些项目来说,该测试方式可能足够。然而,当你在一个由多个开发人员、包含数千或数万行代码、与 web 服务、第三方库、多个数据库进行集成、需要几个月的开发和需求变更等因素的项目上工作时,其他因素才显得重要。手动测试是远远不够的。在任何真正复杂的项目中,一个地方的更改经常会导致其他地方出现无法预料的结果。适当的架构有助于缓解这个问题,但自动化测试也有帮助(并有助于识别可以改进架构的点),它可以识别一个地方的更改是否会影响其他地方。

我的问题是编写测试的成本相比好处似乎非常高。话虽如此,欢迎任何详细的回答,因为我可能漏掉了某些好处。

我将列举一些其他好处。

如果你先进行测试(测试驱动开发),那么你的代码可能会更好。我还没有遇到过一个程序员,他们认真尝试后不是这种情况。首先进行测试可以强制你思考问题并真正设计出解决方案,而不是胡乱编写代码。此外,它会迫使你充分了解问题领域,以便在必须胡乱编写代码时,你知道你的代码在你定义的限制范围内工作。

如果你有完整的测试覆盖率,你可以无风险地进行重构。如果软件问题非常复杂(再次说明,持续几个月的真实项目往往很复杂),那么你可能希望简化先前编写的代码。因此,你可以编写新代码来替换旧代码,如果它通过了所有测试,那么就完成了。它与旧代码相比在测试方面的功能完全相同。对于计划使用敏捷开发方法的项目,重构绝对是必不可少的。变化总是需要进行。

总之,自动化测试,特别是测试驱动开发,基本上是一种管理软件开发复杂性的方法。如果你的项目不是非常复杂,则成本可能超过收益(尽管我怀疑)。然而,真实世界的项目往往非常复杂,测试和TDD的结果说明了一切:它们有效。

(如果你好奇,我认为丹·诺斯(Dan North)在行为驱动开发方面的文章非常有帮助:http://dannorth.net/introducing-bdd


感谢您的出色回答。我认为那实际上已经回答了我所有的问题。 - epochwolf
2
如果你有完整的测试覆盖率,你就可以毫无风险地进行重构。这是一个童话故事。完整的代码覆盖并不意味着您已经测试了所有可能的行为,远非如此。您必须使用任何可能输入到其中的数据和状态来测试任何代码片段(以及它们之间的所有交互) - 这种“行为”覆盖范围实现起来是根本不可能的。您可以达到一个错过更改的负面影响的风险较低的程度,但从未降至零。 - Michael Borgwardt
4
虽然迈克尔说得没错,但攻击修辞夸张是一种简单且大部分情况下并不必要的做法;这更像是销售宣传而非证明。而关键词是“在测试方面”。 - Ian Terrell

2
我并没有使用过Rails,但我认为这些自动化测试可以作为冒烟测试,确保您刚刚完成的工作不会破坏上周完成的内容。随着项目的发展,这将变得越来越重要。
此外,在编写代码之前编写测试(使用测试驱动开发模型)将帮助您更好、更快地编写代码,因为测试强制您充分思考问题。它还将帮助您知道如何将复杂的方法拆分成可以单独测试的较小方法。
您说得对,编写和维护测试需要很多时间。有时比编写代码本身还要花费更多的时间。然而,出于上述原因,它可以节省您在错误修复和重构方面的时间。

2

测试应该验证您的应用程序逻辑。就个人而言,我认为最重要的测试是在Selenium中运行的测试。它们检查浏览器中显示的内容是否与我期望看到的内容相同。但是,如果那是我全部拥有的,那么我会发现很难调试 - 还需要更低级别的测试以及集成、功能和单元测试都是有用的工具。单元测试可以让您检查模型的行为是否符合您的预期(这意味着每种方法,而不仅仅是验证)。验证肯定会正常工作,但只有当您正确使用它们时才会如此。如果您使用错误,则它们将正常工作,但不会按照您的预期方式。编写几行测试比以后调试更快。

http://wiseheartdesign.com/2006/01/16/testing-rails-validations上的简单示例只检查单元测试中的验证。O'Reilly文章http://www.oreillynet.com/pub/a/ruby/2007/06/07/rails-testing-not-just-for-the-paranoid.html?page=1更加详细(尽管仍然相当基本)。

自动化测试在回归测试中特别有用,其中您更改某些内容并运行一组测试来检查是否没有其他任何问题。

测试是重复的一种形式,但它们不违反DRY原则,因为它们以不同的方式表达事物。测试说“我做了X,所以应该发生Y”。代码说“X发生了,现在我需要做Z,这恰好导致Y发生”。即测试刺激原因并检查效果,而代码响应原因并产生效果。


2
Rails生成器创建的大部分测试教程和示例测试都相当无聊,而且在我看来,这可能会让人们错误地认为你应该测试Rails内置方法等愚蠢的东西。既然Rails有自己的测试套件,那么你编写或运行仅测试内置Rails功能的测试是没有意义的。你的测试应该对你正在编写的代码进行测试! :-)
至于测试与只在浏览器中刷新之间的相对价值...你的应用程序越大,手动通过众多情况和边缘情况进行检查以确保应用程序没有出错就越麻烦。最终,你将停止在每次更改后测试整个应用程序,并开始“点对点测试”你认为应该受到影响的区域。不可避免的是,你会发现几个月前完全正常的东西现在已经彻底崩溃了,你不确定它何时崩溃或哪些更改导致了它的崩溃。如果这种情况发生了足够多的次数......你就会开始重视自动化测试.... :-)

1
例如: 我正在处理一个超过25000行代码的项目(是的,在Rails 1.2中),上个周一,有人告诉我如果用户将"leave_date"属性设置为过去日期,那么我能否使他们从除管理员之外的所有列表中消失。
你可以重写每个列表操作(50个以上)来放置
@users.reject!{|u| Date.today > u.leave_date}
或者你可以覆盖"find"方法(DRY;-),但只有在测试了所有查找用户的内容之后,你才会知道你没有通过User#find覆盖突破任何东西!!

1
非常好的建议。如果你想要重写find方法,我会推荐使用ActsAsParanoid插件。祝你好运! - epochwolf

1
我读到的所有关于它的内容都只是展示测试验证。Rails中的验证不应该只是有效吗?这似乎更像是测试框架而不是测试你的代码。为什么需要测试验证呢?
有一个很好的Railscast展示了一种测试控制器的方法。

一位朋友把124个Railscasts扔给我了,我最终会找时间看的。 - epochwolf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接