单元测试有什么意义?

时间:2012-08-11 20:17:06

标签: java testing

我最近加入了一个大量使用单元测试的团队。没有人可以向我解释为什么这种形式的测试如此重要,但他们将其视为法律。

我理解自动化测试的想法是为了防止回归,但我不认为这首先是一个问题。模块化,面向对象,简洁的代码,评论良好,没有回归问题。如果你是第一次正确构建它,并设计出将来发生的不可避免的功能增加,你将永远不需要测试。

而且,这不是优雅的错误处理和日志记录应该完成的吗?当你可以确保所有外部依赖关系首先仔细检查它们的可用性时,为什么要花几周时间来断言断言语句和单元测试呢?

我是否傲慢得出结论,单元测试是“坏”代码库的拐杖,这些代码库存在缺陷并且构建不良?

这是一个严肃的问题。我在任何地方都找不到任何好的答案,如果我质疑自动化测试的目的,我问的每个人似乎都认为我是一个巨魔。

编辑:谢谢你的回答,我想我现在明白了。我看到有几个人投票删除,但我要感谢那些回答的人;它真的有帮助!

9 个答案:

答案 0 :(得分:38)

没有人是完美的 - 你最终会犯错误。单元测试旨在捕获并查明错误的位置,以便:

  • 增加对您编写的代码正确性的信心
  • 增加对重构正确性的信心
  • 跟踪在测试阶段引入了一个更简单的错误

错误处理和日志记录仅在触发错误时有帮助; 单元测试是在测试中触发错误而不是生产的原因。


考虑以下内容......

你有一个包含3个不同部分的软件,每个部分有2个不同的选项。

     A      C      E
    / \    / \    / \
in-<   >--<   >--<   >-out
    \ /    \ /    \ /
     B      D      F

你可以通过手动输入输入和检查输出来测试它 - 首先你输入一些触发A,C,E的输入;然后你会放入A,C,F等等,直到你通过B,D,F覆盖所有内容。

但请记住,B,D和F每个都有自己独立的参数和流量需要测试 - 我们会说每个参数和流量可能有10个。因此,您需要检查至少10*10*10 = 1000个不同的输入,表示A,C,E情况。通过这6个组件有8种不同的可能流量,因此您需要检查 8000 不同的输入组合,以确保您能够获得所有不同的输入。

另一方面,你可以进行单元测试。如果您清楚地定义每个组件的单元边界,那么您可以为A编写10个单元测试,为B编写10个单元测试,依此类推,测试这些边界。这为组件提供了总共60个单元测试,以及少量(例如每个流量为5个,因此为40个)集成测试,确保所有组件都正确连接在一起。这总共有 100 测试,可以有效地实现相同的功能覆盖。

通过使用单元测试,您可以将获得相同数量的覆盖所需的测试量减少约 80x !这是一个相对简单的系统。现在考虑更复杂的软件,其中组件的数量几乎肯定大于6,并且这些组件处理的可能情况的数量几乎肯定大于10.从单元测试而不仅仅是集成测试中获得的节省不断增加。

答案 1 :(得分:18)

简短的回答:是的,你是傲慢的。 ;)

假设你真的 完美,你的代码不仅在你编写它时是正确和完美的,而且它还考虑了将来所有要求。

现在....你如何知道你的代码是完美和正确的? 你需要测试它。如果尚未经过测试,则您无法信任它的工作原理。

这不仅仅是关于回归(因为这意味着代码使用工作。如果它永远不会工作怎么办?当它第一次写入时它是错误的)

  

我理解自动化测试的想法是为了防止回归,但我不认为这首先是一个问题。模块化,面向对象,简洁的代码,评论良好,没有回归问题。

谁告诉过你的?那个人应该被鞭打。 面向对象的代码与其他任何代码一样容易出错。没有什么神奇之处,它不是银弹。在一天结束时,无论何时更改一段代码,都有可能在某个地方破坏某些东西。根据所讨论的代码,机会可能更大或更小,但无论代码有多好,你都不能确定你没有引入回归,除非你测试它。

  

如果你是第一次正确构建它,并且设计了将来发生的不可避免的功能增加,你将永远不需要测试。

你是如何在第一次正确构建它的?正如我上面所说,为此,您需要进行测试,以向您显示代码的工作原理。但更重要的是,您如何“设计”将来会添加的功能?你甚至不知道它们到底是什么。

  

而且,这不是优雅的错误处理和日志记录应该完成的吗?当你可以确保所有外部依赖关系首先仔细检查它们的可用性时,为什么要花几周时间来断言断言语句和单元测试呢?

不,完全没有。

您的代码当然应该处理错误情况,它应该记录您需要记录的内容。

但是你仍然需要知道它能正确地做到这一切。而且您需要知道它也正确处理非错误条件!很高兴知道“如果SQL服务器不可用,我们会向用户显示一条很好的错误消息并退出”。但是如果 可用呢?那你的申请是否有效?

对于任何重要的应用程序,都有很多可能出错的东西。有很多功能,很多代码和许多不同的执行路径。

尝试手动测试它永远不会运用所有这些代码路径。它永远不会在每个环境中测试每个功能的每个方面。即使它确实如此,它只是告诉你“代码今天工作”。明天会工作吗?你怎么知道的?当然,你的直觉可能会告诉你“我从那以后犯下的代码没有破坏任何东西”,但你怎么知道呢?你需要再次测试它。然后再次。再一次。

你问单元测试是否是错误代码库的拐杖。他们是相反的。他们是检查,医生访问,防止代码库变坏。他们不只是告诉你代码是否有效,而是工作时,更重要的是,当停止工作时。你不认为你会引入任何回归吗?你有多确定吗?你能承受错吗?

答案 2 :(得分:7)

当您第一次开始编写代码时,它看起来很简单,并且不需要自动化测试。

但随着时间的推移,您的代码会增长,需求会发生变化,团队会发生变化。对自动化测试的需求也将增长。如果没有自动化测试,开发人员将害怕重构代码 - 特别是如果他们不是编写代码的人。即使使用精心设计的代码添加新功能也可能破坏现有功能。但代码并不总是经过精心设计。在实践中,您可能需要妥协。由于各种原因,您的某些代码将不像您想要的那样干净和可维护。当您意识到需要自动化测试时,可能无法添加它们,因为您的某些类可能难以测试。要添加第一个自动化测试,您可能必须首先重写或重构大量代码。

如果您从一开始就进行自动化测试,这将确保您将代码设计为自动测试

答案 3 :(得分:3)

我向我的一位教授和长期导师提出同样的问题,他发誓说,他提到的或者在这里提到的很多人都是这样的:

他说测试可以是记录代码的好方法,如果你有兴趣看看代码应该如何看待测试,它会让你知道它试图做什么而不必弄清楚它的复杂性,同时你可以检查它是否真的这样做,所以它赢了...... :)

虽然我有一种感觉他只是说,因为他知道我不喜欢评论代码但更喜欢自我记录的源代码,仍然有点有趣的观点;)

作为旁注,您还应该看看coverage,当您进行单元测试或设计测试套件时,您希望覆盖率尽可能接近100%,这意味着测试已经测试了100%您的代码,包括它可以采用的所有不同路径,这是非常具有挑战性的,并且可以使您的测试代码比源代码大几倍,尽管您也可以自动化您的测试代码,一个简单的例子就是测试一个sql数据库,你可以创建一个程序,生成所有可能的sql语句并测试它们是否正确执行,我认为sqlite3有超过9100万行测试,http://www.sqlite.org/testing.html非常有趣......哦,这个he也提倡

答案 4 :(得分:3)

  

我理解自动化测试的想法是为了防止回归,但我不认为这首先是一个问题。

除了已经给出的好建议之外,我还要补充说回归是不可避免的。在这个行业中工作的时间很短 - 很长,足以经历多个发布周期 - 而且你会发现,由于各种不同的原因,旧的bug会再次出现。

一个特别的原因:程序员倾向于一遍又一遍地犯同样的简单错误。例如:当你的一个开发人员正在重新考虑该日期处理代码时,他将继续做出错误的假设,例如: 2月总是有29天,甚至更糟,所有月份都是相同的长度。

回归测试是你防范此类事情的唯一方法。

答案 5 :(得分:3)

好的,所以我之前的想法有点像你现在所做的那样,所以我想我会评论你的一些陈述,只是对你的陈述给出另一种观点。也许这可以说明测试驱动开发的不同编码方法。

  

我理解自动化测试的想法是防止回归,但我没有看到   这首先是一个问题。

回归通常不仅仅是对以前对代码的工作原理认识不足而进行的修改吗?如果是,你如何确保 所有开发人员都以这样的方式记录其代码的功能 自动检测是否有人在后期进行破坏性更改? 单元测试可能有帮助吗?

  

模块化,面向对象,简洁的代码   评论良好的回归没有问题。

如果您认为文档写得非常好以至于几乎不可能误解它并且对所述程序的每个后续修改都是通过充分的手术方式完成的,那么回归可能没有问题。 熟练的编码人员,事实上它确实没有改变旧的行为。

如果您将单元测试视为记录行为的一种方式,那么它是否应该是平等的 或者更有效的方式来传达旧代码的功能?

测试当然不应该排除文档,但每个工具都有自己的专业知识 利弊。

  

如果你正确建立它   第一次,设计的功能不可避免的增加发生在   未来,你永远不需要测试。

从中构建一切正确且灵活的成本和精力 从我的观点来看,这一点很大,往往导致两件事:一个过于复杂的系统和一个缓慢的开发速度。

问问自己:灵活性的成本是多少?您是否通过为每个问题构建多个解决方案并促进它们之间的运行时切换来最小化风险?或者您是否正在构建允许在运行时进行修补行为的插件系统?这两种策略在开发时都非常昂贵,而且增加了项目的复杂性。

如果您现在正在构建您需要的东西,并且尽可能保持足够的灵活性,以便在需要时快速编写任何修改代码,您将在每次实施时节省多少时间和精力?

如果您对现有功能进行了测试,那么扩展功能会变得更容易吗?

  

而且,这不是优雅的错误处理和记录应该是什么   完成?为什么要花费数周的时间来完成断言语句和单元测试   确保所有外部依赖项首先仔细检查其可用性?

优雅的错误处理和日志记录可能是一个良好的后备,但故障恢复永远不会取代正确性。我觉得我无法进一步发表评论,因为当你说“优雅错误处理”和“重复检查可用性”时,我并不完全确定你所指的是什么,但这听起来像是对我的恢复。没有错误比能够从中恢复要好得多。

  

我是否正在得出结论,单位测试是“坏”的拐杖   有缺陷且构建不良的代码库?这是一个严重的问题。我找不到任何东西   在任何地方都有好的答案,我问的每个人似乎都认为如果我提问的话,我就是一个巨魔   自动化测试的目的。

您当然可以使用单元测试作为避免文档和良好编码原则的借口。我在很多地方见过这个。但它也是一个很棒的工具,可以添加到您的工具箱中,以提高产品的正确性,简单性和灵活性!祝你的新团队好运。我相信你可以教他们一些关于良好文档和其他有助于避免错误的原则,他们可能会带来一些原则,这些原则可以帮助您更快,更省力地开发产品,同时避免指数级的复杂性增长。

答案 6 :(得分:3)

我还要再添加两位:

  1. 我发现单元测试的最大好处是当我必须编写作为我的代码客户端的测试时发生的微妙转变。如果难以测试,则很难使用。我经常进行界面更改,最终使用户的生活变得更好。
  2. 编写单元测试是一种文档形式。它告诉人们“这里是如何使用(和滥用)我的课程。”未来的客户应该有很多关于如何使用代码的内置示例;单元测试给他们。

答案 7 :(得分:1)

  

模块化,面向对象,简洁的代码,评论不详   有回归问题。

即使评论很好,评论也仅描述了预期的行为。函数可以对线性集合进行排序,但使用bubblesort而不是quicksort。在这一点上,评论变得没有什么比一个红鲱鱼更好(特别是如果你得到一个投诉,排序需要永远,但你争辩说,一个小时后,它正确排序)。很可能原因是排序算法的原始设计者打算在非常小的列表上使用bubblesort,然后在较大的列表上切换到quicksort,但是没有测试来验证这一点,你无法在不深入检查代码的情况下进行检查基

此外,代码库的功能和规格可能会在一小时内发生变化,具体取决于您或您的商店所做的事情。该文件是实际发展的主要障碍。什么更好将有一套测试用例来确保缺乏回归。

  

如果你是第一次正确构建,并为不可避免的设计   功能的泥泞增加了未来发生的事情,你永远不会需要   测试

两个词:敏捷开发。设计只是“足够”,并且添加了比当时指定的功能更多的功能,这在很大程度上违反了YAGNI。与开发主要案例相比,你最终花费更多的时间来开发边缘案例。

  

而且,这不是优雅的错误处理和日志记录   应该完成?为什么要花几周的时间来断言断言   和单元测试时,你可以确保你所有的外部   依赖项首先仔细检查它们的可用性?

错误处理告诉您出错了。单元测试可以告诉你在哪里。您只能通过正确的测试确保所有内容都与您的代码库一致。

任何人都可以编写混淆代码并告诉您它是正确的。任何继承该代码库的人都无法分辨。单元测试有助于缓解此问题。

  

我很满意地得出单元测试的结论   对于那些有缺陷且构建不佳的“坏”代码库的问题?

有点儿。代码库不一定是“坏”需要单元测试,它们只需要改变。 (更改代码库和不需要单元测试的代码不能存在于同一维度中。)

您未考虑的另一种情况是您继承的代码库,并且您的客户声称功能缺失或无法正常工作。它可能是一两件,但它们有一些非常时髦的依赖;你不能只拿你最喜欢的编辑器并用大锤来完成它。为了保证您不会影响暂时的代码,并希望修复/实现新代码,您必须为要更改的模块编写回归测试。< / p>

同样的情况是,修复错误的代价越高,它传播的链越往上走。在开发中发现的错误比在QA中发现的错误要比发布候选版本中发现的错误要便得多,而不是公开发布中发现的错误。

这里还有一些其他问题,例如编程成本(维护成本是其中的主要因素,而不是新开发),以及为遗留代码添加新功能,但在大多数情况下,它是<强大>非常重要的是单元测试完成。如果你在一个没有某种类型单位回归的代码库中工作,那么你将花费太多时间来修复测试可能涵盖的错误。

答案 8 :(得分:-1)

100%的单元测试覆盖率允许您从两个不同的视点执行代码。通常,您的代码仅在程序运行时执行。单元测试以另一种更模块化的方式测试流量。想一想会计中的复式记账。 (我听说过它是现代资本主义的基石)这里每个事件在两个不同的分类账中出现两次。最后,余额应为零。这种形式的簿记是doulbe一直在检查自己。这就是我对单元测试的看法。

执行测试第一次设计我也发现我的代码清晰,管理更多。通过先写一个测试,我可以准确地考虑应该做什么类。