【问题标题】:How slow is too slow for unit tests?对于单元测试来说有多慢是太慢了?
【发布时间】:2010-09-29 18:35:03
【问题描述】:

Michael Feathers,在 Working Effectively With Legacy Code,第 13-14 页提到:

需要 1/10 的单元测试 第二个运行是一个缓慢的单元测试...... 如果 [单元测试] 运行不快,他们 不是单元测试。

如果一个人有 30,000 次测试,我可以理解为什么 1/10 秒太慢,因为它需要将近一个小时才能运行。但是,这是否意味着 1/11 秒更好?不,不是真的(因为它只快了 5 分钟)。因此,硬性规定可能并不完美。

因此,当考虑到单元测试的速度有多慢时,也许我应该重新表述这个问题。 对于开发人员来说等待单元测试套件完成的时间太长了?

举一个测试速度的例子。看看几个 MSTest 单元测试持续时间:

0.2637638 seconds
0.0589954
0.0272193
0.0209824
0.0199389
0.0088322
0.0033815
0.0028137
0.0027601
0.0008775
0.0008171
0.0007351
0.0007147
0.0005898
0.0004937
0.0004624
0.00045
0.0004397
0.0004385
0.0004376
0.0003329

所有 21 个单元测试的平均值为 0.019785 秒。请注意,最慢的测试是由于它使用 Microsoft Moles 来模拟/隔离文件系统。

因此,在这个示例中,如果我的单元测试套件增加到 10,000 个测试,它可能需要超过 3 分钟才能运行。

【问题讨论】:

  • 我认为你的平均值是错误的。
  • 你是对的。我已经调整过了。 0.019785 而不是 0.19785

标签: unit-testing performance mstest


【解决方案1】:

我看过一个这样的项目,其中单元测试的数量使系统需要很长时间才能测试所有内容。 “太长”意味着您基本上没有将其作为正常开发程序的一部分。

但是,他们所做的是将单元测试分为两部分。关键测试,以及“其他一切”。

关键测试只需要几秒钟就可以运行,并且只测试系统中最关键的部分,这里的“关键”意味着“如果这里有问题,一切都会出错”。

使整个运行时间过长的测试被归入“其他所有”部分,并且仅在构建服务器上运行。

每当有人将代码提交到源代码控制存储库时,关键测试将再次首先运行,然后在未来几分钟内安排“全面运行”。如果在该时间间隔内没有人签入代码,则运行完整的测试。诚然,他们不需要 30 分钟,更像是 8-10 分钟。

这是使用 TeamCity 完成的,因此即使一个构建代理正忙于完整的单元测试套件,其他构建代理仍然可以根据需要进行正常提交并运行关键单元测试。

【讨论】:

  • +1 了解您如何定义“太长”。对于 UI 命令,“太长”通常定义为大约 3 秒——用户愿意等待的时间,然后他们要么放弃,要么变得非常恼火。在这种情况下,我们必须记住,我们仍然是用户,您的定义就是这样一个提醒。
  • 我喜欢您将关键用作决定如何拆分测试的维度的想法。然而,临界与速度是正交的。在我们的例子中,我们的测试既“如果这里有问题,一切都会出错”,而且速度很慢,因为它们需要运行 SQL 数据库。 (是的,我们必须执行 SQL 以确定正确性。模拟它会涉及重写 RDBMS。)我们使用 sqlite3,但在我们的 Python 3 项目中也需要 PostgreSQL 和 MySQL 支持。还在想办法……
【解决方案2】:

我只参与过测试套件需要至少 10 分钟才能运行的项目。更大的,更像是几个小时。我们接受了它并等待,因为他们几乎可以保证在你扔给他们的任何东西中找到至少一个问题。这些项目又大又多。

我想知道这些可以在几秒钟内完成全面测试的项目。

(当您的项目的单元测试需要几个小时才能完成任务的秘诀是同时处理四到五件事情。您在测试套件中抛出一组补丁并切换任务,当你完成你切换到的东西时,也许你的结果已经回来了。)

【讨论】:

  • 听起来你不明白UNIT测试和INTEGRATION测试的区别。根据定义,单元测试应该测试一个代码单元(名称如何表明其目的)。使用 MVC 示例,如果您有一个控制器测试在业务层中实例化一个(或多个)对象,而后者又在您的存储库层中实例化一个或多个对象,则您没有 UNIT 测试。你有一个集成测试。虽然实例化模拟对象不是“免费的”,但好的模拟框架仍然比访问您的存储库快得多。
  • 如果您有一个真正只包含 UNIT 测试的测试套件,并且仍然需要几个小时才能运行,那么您的企业架构师就失败了,因为他们允许您创建一个严重的单体系统,而该系统应该是分解成将功能公开为服务的部分。
  • 我猜您从未使用过,例如,Web 浏览器。在 Firefox 中验证 CSS 解析器,全部单独——没有相关功能,只是“解析它并检查结果数据结构是否是它应该是的”——涉及 100,000 次测试,其中总共需要大约五分钟的时间来运行。这就是 CSS 语法的庞大和复杂程度。
  • 你完全正确!最初我认为两种文化可能有点过于极端,但我承认我想不出一个不那么强调的术语,仍然能传达同样程度的区别。因此,对于那些从事超酷、超级复杂的东西(如 Web 浏览器或 css 解析器)的人来说,请从 Zack 那里获得启发。对于我们这些在 LOB 应用程序上工作的普通人来说,再看看你的测试并弄清楚你是否有 UNIT 测试或 INTEGRATION 测试。
  • 即使您将一个大型项目分解为模块,除非您同时删除一些单元测试,否则这些测试仍将花费相同的时间来运行。即使你所有的测试都是单元测试,如果你有很多复杂的行为,你也会有很多测试来测试它。如果你大量使用模拟,分解成模块来添加一些抽象不会帮助你的测试(虽然它可能有助于理解代码,所以无论如何都要这样做。)
【解决方案3】:

我有一些单元测试需要几秒钟才能执行。我有一种方法可以进行非常复杂的计算和数十亿次的操作。当我们重构这个棘手且超快的方法时,我们使用一些已知的好值作为单元测试的基础(我们必须优化其中的废话,因为正如我所说,它正在执行数十亿次计算)。

规则并不适用于每个领域/问题空间。

我们不能将此方法“划分”为可以进行单元测试的更小的方法:它是一种微小但非常复杂的方法(使用无法快速重新创建的超大预计算表等等)。

我们对该方法进行了单元测试。它们是单元测试。他们需要几秒钟来执行。这是一件好事 [TM]。

当然,我不反对您将 JUnit 之类的单元测试库用于非单元测试的事情:例如,我们还使用 JUnit 来测试复杂的多线程场景。这些不是“单元测试”,但你敢打赌 JUnit 仍然统治着这一天:)

【讨论】:

    【解决方案4】:

    编辑查看我对另一个答案 (Link) 的评论。请注意,关于单元测试有很多来回,所以在您决定赞成或反对这个答案之前,请阅读该答案的所有 cmets。

    接下来,使用像 Might-Moose(Mighty-Moose 已被放弃,但还有其他工具)这样的工具,每次签入文件时只运行受代码更改影响的测试(而不是整个测试库) .

    【讨论】:

    • “见我对 Zack 的回答的评论”使这个答​​案很差。我现在正在阅读它,但我不知道“Zack 的答案”在哪里
    • 好点。 Zack 将他的用户名更改为 zwol。答案发布在“2010 年 9 月 29 日 19:00 已回答”,我的评论添加到“2012 年 11 月 7 日 15:07”
    【解决方案5】:

    那么你的问题是什么? :-) 我同意,这里的真正衡量标准是开发人员必须等待完整运行单元测试的时间。太长了,他们会在提交代码之前开始偷工减料。我希望看到一个完整的提交构建需要不到一两分钟,但这并不总是可能的。在我的工作中,一个提交构建过去需要 8 分钟,而且人们在提交之前只开始运行其中的一小部分 - 所以我们购买了更强大的机器:-)

    【讨论】:

      【解决方案6】:

      对于开发人员来说等待单元测试套件完成的时间太长了? 这真的取决于开发人员乐于等待他们的更改反馈的时间。我会说,如果你开始说几分钟的话,那就太慢了,你可能应该将测试套件分解成单独的测试项目并分别运行它们。

      【讨论】:

        猜你喜欢
        • 2020-08-08
        • 1970-01-01
        • 1970-01-01
        • 2020-10-03
        • 2016-05-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-19
        相关资源
        最近更新 更多