我们如何使单元测试快速运行?


40

在项目中,我们已经进行了将近一千次测试,并且人们花了很长时间才停止在进行检入之前运行它们的麻烦。充其量,他们运行与所更改代码段相关的测试,而最糟糕的是,它们仅在未经测试的情况下检入。

我认为这个问题是由于以下事实导致的:解决方案已增长到120个项目(我们通常会做很多较小的项目,这只是我们第二次正确进行TDD),并且构建+测试时间已增长到大约两三分钟在较小的机器上。

我们如何减少测试的运行时间?有技巧吗?假装更多?少假装?也许在运行所有测试时不应该自动运行较大的集成测试?

编辑:作为对几个答案的回应,我们已经使用CI和构建服务器,这就是我知道测试失败的方式。问题(实际上是一种症状)是我们不断收到有关构建失败的消息。大多数人会执行部分测试,但并非全部。关于测试,它们实际上做得很好,对所有东西都使用伪造品,根本没有IO。


8
获得更好的硬件?与程序员相比,硬件便宜。
Bryan Oakley 2013年

18
您已经在问题中隐含了解决方案:仅运行与更改的代码段相关的测试。作为质量检查/发布周期的一部分,定期运行整个测试套件。就是说,2到3分钟听起来并不多,所以您的开发团队可能检查得太频繁了。
罗伯特·哈维

3
第一个基准,以找出性能成本的来源。有一些昂贵的测试,还是数量庞大的测试?某些设置昂贵吗?
CodesInChaos

13
该死,我希望我们的测试只有2-3分钟。要运行我们的所有单元测试,需要25分钟-我们还没有任何集成测试。
Izkata

4
2到3分钟?真是的 我们的可以运行数小时...
冷冻豌豆的罗迪2013年

Answers:


51

一种可能的解决方案是使用某种风格的版本控制软件(gitsvn等)将测试部分从开发机移至持续集成设置(例如Jenkins)。

当必须编写新代码时,给定的开发人员将为他们在存储库中所做的任何事情创建一个分支。所有工作都将在此分支中完成,他们可以随时将更改提交给分支,而不会弄乱代码的主线。

当给定的功能,错误修复或它们正在执行的其他任何工作完成时,可以将分支合并回运行所有单元测试的主干(或者您更喜欢这样做)。如果测试失败,则合并将被拒绝,并通知开发人员,以便他们可以纠正错误。

您也可以在进行提交时让CI服务器在每个功能分支上运行单元测试。这样,开发人员可以进行一些更改,提交代码,并让服务器在继续进行其他更改或其他项目的同时在后台运行测试。

可以在这里找到进行这种设置的一种很好的指南(特定于git,但应该适用于其他版本控制系统):http : //nvie.com/posts/a-successful-git-branching-model/


15
这个。如果开发人员“已停止与做在检查之前运行它们(单元测试)打扰”,那么你希望你的CI设置来运行他们之后的检查。
Carson63000

+1:进一步的改进是将测试模块化。如果自上次运行以来未更改特定模块/文件,则没有理由重新运行负责对其进行测试的测试。有点像makefile,不会仅因为一个文件已更改而重新编译所有内容。这可能需要一些工作,但也可能会为您提供更清洁的测试。
狮子座

分支方法是否可以与TFS一起使用?我们用TFS编写C#,在TFS中分支比在git中友好。我相信这个想法甚至会被拒绝,因为我们从不做分支。
2013年

我没有使用TFS的个人经验;但是,我能够从Microsoft那里获得这份指南,该指南似乎显示了与该指南类似的分支策略: msdn.microsoft.com/en-us/magazine/gg598921.aspx
Mike

33

大多数单元测试应在10毫秒左右的时间内完成。有“近千名测试”是什么,应该采取可能几秒钟就可以运行。

如果不是,那么您应该停止编写高度耦合的集成测试(除非代码需要如此),并开始编写良好的单元测试(从良好解耦的代码开始,并正确使用伪造/模仿/存根/等)。这种耦合将影响测试质量以及编写它们的时间-因此,这不仅仅是减少测试运行时间的问题。


30
好吧,您可能不应该停止编写集成测试和其他非单元自动测试,因为它们本身很有用。您只是不应该将它们与单元测试混淆,并将它们分开,部分是因为它们比较慢。

2
您正确地说,这些似乎是集成测试。
Tom Squires

9
这个答案没有效果。首先,它设定了不合理的期望。单元测试框架本身就有开销。每次测试的时间少于一毫秒,并不意味着一千次测试的时间必须少于几秒钟。在大多数情况下,OP的整个测试套件在2-3分钟内完成是一个很好的信号。
rwong

6
@rwong-对不起,我叫废话。我得到的指标来自运行两个可用的专业项目:一个项目进行约300个测试,一个项目进行约30000个测试,并查看测试运行时。一个测试套件要花费2-3分钟进行<1000个测试,这是残酷的,这表明测试没有得到足够的隔离。
Telastyn 2013年

2
@rwong与Telastyn一样,这是我的一个数据点:即使进行了许多比理想的测试大的测试,测试框架(py.test)在后台仍会产生大量魔力,而所有内容都是纯Python代码(“ 100x速度比C慢),在我的一个项目中运行大约500次测试,在使用了几年的慢速上网本上只需不到6秒。这个数字在测试数量上大致是线性的。尽管存在一些启动开销,但所有测试均将其摊销,并且每次测试的开销为O(1)。

16

我已经使用几种方法来解决类似的问题:

  1. 检查执行时间,找到所有最慢的测试,然后分析为什么花那么多时间执行
  2. 您有100个项目,也许您不需要每次都构建和测试它们吗?您只能在一个晚上进行构建时运行所有单元测试吗?创建多个“快速”构建配置以供日常使用。CI服务器将仅执行与当前开发过程的“热门”部分相关的有限的单元测试项目集
  3. 模拟并隔离一切,尽可能避免使用磁盘/网络I / O
  4. 当无法隔离此类操作时,您是否可以进行集成测试?也许您可以仅将集成测试安排到夜间构建
  5. 检查所有偶尔的单例,这些单例保留对实例/资源的引用并消耗内存,这可能会在运行所有测试时导致性能下降。

此外,您可以使用以下工具简化生活并加快测试速度

  1. 门控提交一些CI服务器可以配置为在将代码提交到源存储库之前执行构建和测试。如果某人在提交代码时未事先运行所有测试(其中还包含失败的测试),则该代码将被拒绝并返回给作者。
  2. 配置CI服务器使用多个计算机或进程并行执行测试。示例是pnunit带有多个节点的CI配置。
  3. 针对开发人员的持续测试插件,它将在编写代码时自动运行所有测试。

12

0.听你的程序员。

如果他们没有运行测试,则意味着他们认为成本(等待测试运行,处理错误错误)大于价值(立即捕获错误)。降低成本,增加价值,人们将一直进行测试。

1.使您的测试100%可靠。

如果您的测试因假阴性而失败,请立即处理。修理它们,更改它们,消除它们,以确保100%可靠性的一切所需。(可以进行一组不可靠但仍然有用的测试,可以单独运行,但是测试的主体必须可靠。)

2.更改系统,以确保所有测试始终通过。

使用连续集成系统以确保仅将通过的提交合并到main / official / release / whatever分支中。

3.改变您的文化以重视100%通过测试。

上一课,一个任务只有在100%的测试通过并被合并到main / official / release / whatever分支中之后才能“完成”。

4.快速进行测试。

我从事的项目需要花一秒钟的时间,而整个项目则需要一整天的时间。运行测试所花费的时间与我的工作效率之间有着很强的相关性。

测试花费的时间越长,运行它们的频率就越低。这意味着您将花费更长的时间,而不会收到有关所做更改的反馈。这也意味着两次提交之间的距离会更长。承诺更多意味着更小的步骤更易于合并;提交历史更容易遵循;在历史中查找错误更容易;回滚也更容易。

想象一下测试运行得如此之快,以至于您不必介意每次编译时都自动运行它们。

快速进行测试可能很难(这就是OP所要求的,对!)。去耦是关键。模仿/伪造还可以,但我认为您可以通过重构使模仿/伪造变得不必要而做得更好。请参阅http://arlobelshee.com/post/the-no-mocks-book开头的Arlo Belshee博客。

5.使测试有用。

如果拧紧后测试没有失败,那有什么意义呢?教自己编写测试,以捕获您可能会创建的错误。这本身就是一项技能,将引起很多关注。


2
强烈同意,尤其是第3点和第1点。如果开发人员未在运行测试,则测试将被破坏,环境被破坏或同时被破坏。点1是最小值。错误的失败比缺少测试更糟糕。因为人们学会接受失败。一旦容忍了失败,它就会蔓延开来,并且需要付出巨大的努力才能恢复到100%通过,并期望100%通过。从今天开始修复此问题
法案四

而且您怎么可能不同意#5?!?除了1和3或2和4,还可以!无论如何,各地都很好的答案。
fourpastmidnight

4

几分钟可以进行单元测试。但是,请记住,有3种主要测试类型:

  1. 单元测试-独立于项目的其余部分测试每个“单元”(类或方法)
  2. 集成测试-通常通过调用程序来测试整个项目。我见过的一些项目将此与回归测试结合在一起。这里的模拟比单元测试少得多
  3. 回归测试-测试整个套件,因为测试套件是最终用户。如果您有控制台应用程序,则可以使用控制台来运行和测试程序。您从不向内部公开这些测试,并且程序的任何最终用户(理论上)都应该能够运行您的回归测试套件(即使它们永远也不会)

这些是按速度顺序列出的。单元测试应该很快。他们不会捕获所有错误,但是他们确定该程序相当不错。单元测试应该在3分钟或更短的时间内运行,或者运行良好的硬件。您说您只有1000个单元测试,而这些测试需要2-3分钟?好吧,那也许还可以。

检查事项:

  • 但是,请确保将单元测试和集成测试分开。集成测试将总是较慢。

  • 确保您的单元测试正在并行运行。没有理由让他们不要拒绝他们是否是真正的单元测试

  • 确保您的单元测试是“无依赖的”。他们永远不要访问数据库或文件系统

除此之外,您的测试现在听起来还不错。但是,作为参考,我的一个Microsoft团队中的一位朋友在4000分钟的测试中,在不错的硬件上运行了4,000个单元测试(这是一个复杂的项目)。可以进行快速的单元测试。消除依赖关系(并且仅根据需要进行模拟)是提高速度的主要方法。


3

个人软件过程(PSP)上对开发人员进行培训,以帮助他们通过使用更多学科来理解和提高其性能。编写代码与用手指敲键盘,然后按下编译并签入按钮无关。

PSP过去曾经很流行,当时编译代码是一个耗时的过程(在大型机上数小时/数天,因此每个人都必须共享编译器)。但是,当个人工作站变得更加强大时,我们所有人都接受了该过程:

  1. 不用思考就输入一些代码
  2. 命中构建/编译
  3. 修复语法以使其可编译
  4. 运行测试以查看您编写的内容是否真正有意义

如果您认为在键入之前,然后在键入之后,请检查所编写的内容,则可以在运行构建和测试套件之前减少错误的数量。学会不要每天按50次构建,而应按一次或两次,那么构建和测试时间要花几分钟的时间就不再那么重要了。


2
我完全同意您的清单,但绝对不同意“每天只运行两次优于50次”。
Doc Brown

3

一种可能的方法:拆分解决方案。如果一个解决方案有100个项目,那将是很难管理的。仅仅因为两个项目(例如A和B)使用另一个项目(例如Lib)中的一些通用代码,并不意味着它们必须处于同一解决方案中。

相反,您可以使用项目A和Lib创建解决方案A,还可以使用项目B和Lib创建解决方案B。


2

我处于类似情况。我有单元测试,用于测试与服务器的通信。他们正在测试超时,取消连接等行为。整个测试过程需要7分钟。

7分钟是一个相对较短的时间,但是您不必在每次提交之前都这样做。

我们还有一组自动化的UI测试,其运行时间为2个小时。您并不是每天都想在计算机上运行它。

那么该怎么办?

  1. 更改测试通常不是很有效。
  2. 提交前仅运行相关测试。
  3. 每天(或每天几次)在构建服务器上运行所有测试。这也使您可以生成良好的代码覆盖率和代码分析报告。

重要的是:您的所有测试都应经常运行,因为发现错误很重要。但是,并不是绝对有必要在提交之前找到它们。


1
关于与服务器对话的测试:如果是与服务器对话,则它并不是真正的单元测试,而是更高的测试。如果您是我,我会分离出单元测试(应该可以快速运行),并至少在每次提交之前运行它们。这样一来,您至少可以在提交代码之前就省去一些快速的工作(不需要与服务器对话的内容)。
迈克尔·科恩

@MichaelKohne我知道有人会发现它。我知道它们不是完全的单元测试,但它们具有相同的目的,只是关于您如何命名它们。
苏珊(Sulthan)2013年

1
大多是关于您如何命名的,但是最好记住差异(无论您使用什么名称)。如果您不区分,那么(以我的经验)开发人员倾向于编写更高级别的测试。在这一点上,您没有得到强制您在抽象和耦合方面变得明智的测试。
迈克尔·科恩

1

尽管您对问题的描述没有全面了解代码库,但我认为我可以肯定地说您的问题有两个方面。

学习编写正确的测试。

您说您有将近一千次测试,并且有120个项目。假设这些项目中最多有一半是测试项目,则对60个生产代码项目有1000个测试。这使您大约进行16-17次测试。项目!!!

这可能是我在生产系统中必须覆盖约1-2个班级的测试量。因此,除非每个项目中只有1-2个类(在这种情况下,您的项目结构过于精细),否则您的测试将太大,它们将覆盖太多的领域。您说这是您正确执行TDD的第一个项目。有人说,您提供的数字表明情况并非如此,您没有执行TDD属性。

您需要学习编写正确的测试,这可能意味着您首先需要学习如何使代码可测试。如果您找不到团队内部的经验来做到这一点,我建议您从外部聘请帮助,例如以一到两名顾问的形式帮助您的团队在2-3个月的时间里学习编写可测试的代码,最少的单元测试。

相比之下,在我目前正在从事的.NET项目中,我们可以在不到10秒的时间内运行大约500个单元测试(这甚至没有在高规格机器上进行测量)。如果这些是您的数据,那么您将不会害怕经常在本地运行这些数据。

学习管理项目结构。

您已将解决方案分为120个项目。按照我的标准,这是一个数量惊人的项目。

因此,如果实际上有那么多项目是有意义的(我觉得没有),但是您的问题没有提供足够的信息来对此做出合格的判断,那么您需要将项目分成较小的部分,可以分别构建,版本控制和部署。因此,当开发人员运行测试套件时,他/她只需要运行与他/她当前正在处理的组件有关的测试。构建服务器应注意验证所有内容是否正确集成。

但是根据我的经验,将项目分解为分别构建,版本化和部署的多个组件需要一个非常成熟的开发团队,这个团队比我觉得您的团队更成熟。

但是无论如何,您需要对项目结构做些事情。要么将项目拆分为单独的组件,要么开始合并项目。

问自己是否真的需要120个项目?

ps您可能想签出NCrunch。这是一个Visual Studio插件,可在后台自动运行测试。


0

JUnit测试通常是快速的,但是其中一些仅需要花费一些时间来执行。

例如,数据库测试通常花费一些时间来初始化和完成。

如果您有数百个测试,即使测试速度很快,由于数量众多,它们也需要大量时间才能运行。

可以做的是:

1)确定关键测试。库最重要部分的内容以及更改后最有可能失败的内容。只有那些测试应该始终在编译时运行。如果某些代码经常被破坏,则即使它们需要花费很长时间才能执行,其测试也必须进行;另一方面,如果软件的某些部分从未引起问题,则可以安全地跳过每个构建的测试。

2)准备连续集成服务器,它将在后台运行所有测试。由您决定是每小时构建一次还是在每次提交之后进行构建(这仅在您要自动检测谁的提交引起麻烦的情况下才有意义)。


0

我看到的问题:

a)使用IOC建立测试元素。70秒-> 7秒(通过删除容器)。

b)不模拟所有课程。将单元测试放在一个元素中。我看过测试经过几节课。这些不是单元测试,更有可能损坏。

c)对他们进行分析,以了解正在发生的事情。我发现构造函数正在构建不需要的东西,因此我将其本地化并减少了运行时间。

d)个人资料。也许代码不是那么好,您可以通过审核获得一些效率。

e)删除依赖项。保持测试可执行文件的大小不变,将减少加载时间。使用接口库和IOC容器来运行最终解决方案,但是您的主要测试项目应仅定义了接口库。这样可以确保分离,确保更容易测试,还可以减小测试脚印。


0

我感到您很痛苦,并且在很多地方可以大大提高构建速度。不过,我建议对事情的数量是衡量在粒度细节来找出你的构建是耗时最长。例如,我有一个包含大约30个项目的构建,该过程只需要花一分钟多的时间。但是,那只是图片的一部分。我也知道哪些项目耗时最长,这有助于我集中精力。

消耗时间的东西:

  • 软件包下载(用于C#的Nuget,用于Java的Maven,用于Ruby的Gem等)
  • 在文件系统上复制大量文件(例如:GDAL支持文件)
  • 打开与数据库的连接(某些连接需要花费一秒钟来进行协商)
  • 基于反射的代码
  • 自动生成的代码
  • 使用异常控制程序流

模拟库使用反射或使用字节码库的注入代码为您生成模拟。虽然非常方便,但它会占用测试时间。如果在测试的循环内生成模拟,则可能会给单元测试增加可测量的时间。

有解决问题的方法:

  • 将涉及数据库的测试移至集成(即仅在CI构建服务器上)
  • 避免在测试中的循环中创建模拟。实际上,完全避免测试中的循环。在这种情况下,使用参数化测试可能会获得相同的结果。
  • 考虑将大规模解决方案拆分为单独的解决方案

当您的解决方案包含100多个项目时,您将拥有库代码,测试和应用程序代码的组合。每个库都可以是它自己的解决方案以及相关的测试。Jet Brains Team City是一台CI构建服务器,是Nuget服务器的两倍,而且我敢肯定它不是唯一的服务器。这使您可以灵活地将那些可能不会经常更改的库移至其自己的解决方案/项目,并使用Nuget来解决应用程序代码的依赖性。较小的解决方案意味着您可以快速,轻松地对库进行更改,并享受主要解决方案带来的好处。


-1

您的测试环境可以在任何地方运行吗?如果可以,请使用云计算来运行测试。在N个虚拟机之间划分测试。如果在一台机器上运行测试的时间为T1秒,那么将它们分开运行的时间T2可能接近T2 = T1 / N。(假设每个测试用例花费大约相同的时间。)并且您只需要在使用VM时为它们付费。这样一来,您就不会在实验室中的24/7处放置一堆测试机。(我很想能够在我的工作地点做到这一点,但我们与特定的硬件绑定在一起。我没有VM。)

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.