在版本控制挂钩中运行单元测试是一种好习惯吗?


43

从技术角度来看,可以添加一些前/后推钩,以在允许某些特定的提交合并到远程默认分支之前运行单元测试。

我的问题是-最好将单元测试保留在构建管道中(因此,将损坏的提交引入仓库),还是最好不要允许“不良”的提交发生。

我确实意识到我不受这两种选择的限制。例如,在将合并提交提交到仓库之前,我可以允许所有提交分支和测试。但是,如果您必须在这两种解决方案之间进行选择,那么您将选择哪种解决方案,以及出于哪些确切原因?


Answers:


35

不,不是,原因有两个:

速度

提交应该很快。例如,一次耗时500毫秒的提交太慢,将鼓励开发人员更加谨慎地提交。鉴于在任何大于Hello World的项目上,您都会进行数十或数百个测试,因此在预提交过程中运行它们将花费太多时间。

当然,对于具有数千个测试的大型项目而言,情况会变得更糟,这些测试在分布式体系结构上运行几分钟,或者在一台计算机上运行数周或数月。

最糟糕的是,您无法做得更快。具有100个单元测试的小型Python项目在普通服务器上至少需要花费一秒钟的时间才能运行,但通常需要更长的时间。对于C#应用程序,由于编译时间的原因,平均时间为4-5秒。

从那时起,您可以额外花一万美元购买一台更好的服务器,这将减少时间,但不会减少太多,或者在多台服务器上运行测试,这只会减慢速度。

当您有成千上万的测试(以及功能,系统和集成测试)时,这两种方法都可以带来很高的回报,从而使它们可以在数分钟而不是数周的时间内运行,但这对于小型项目没有帮助。

相反,您可以做的是:

  • 鼓励开发人员在提交之前运行与他们在本地修改的代码密切相关的测试。他们可能无法运行数千个单元测试,但可以运行五分之十。

    确保找到相关的测试并运行它们实际上很容易(而且很快)。例如,Visual Studio能够检测自上次运行以来所做的更改可能会影响哪些测试。其他IDE /平台/语言/框架可能具有类似的功能。

  • 保持提交尽可能快。强制执行样式规则是可以的,因为通常它是唯一的执行位置,并且因为这样的检查通常非常快。只要保持快速,就可以进行静态分析,这种情况很少发生。运行单元测试不正确。

  • 在Continuous Integration服务器上运行单元测试。

  • 确保在开发人员中断构建时(或在单元测试失败时)自动通知开发人员,如果您将编译器视为检查可以引入代码的某些错误的工具,这几乎是同一回事。

    例如,转到网页检查最后的构建不是解决方案。应该自动通知他们。显示弹出窗口或发送SMS是如何通知它们的两个示例。

  • 确保开发人员了解破坏构建(或使回归测试失败)不是可以的,并且一旦发生,他们的首要任务就是修复它。老板是否要求明天交付高优先级功能并不重要:构建失败,应该对其进行修复。

安全

托管存储库的服务器不应运行自定义代码(例如单元测试),尤其是出于安全原因。这些原因已经在GitLab的同一服务器上的CI运行器中说明了吗?

另一方面,如果您的想法是从预提交钩子在构建服务器上启动进程,那么它将减慢更多的提交速度。


4
我同意,这就是构建服务器的用途。您的源代码管理用于管理源代码,而不是确保您的应用程序正常工作。
马修

4
在极端情况下,报复可能是破坏版本时的必要通知工具。

37
半秒是否太慢?与最终查看正在提交的内容,然后思考并键入适当的提交注释相比,这简直是九牛一毛。
Doval 2014年

5
@Doval:区别在于,当您对视图进行最后一眼或思考时,您会很主动,因此,您不必等待。不是在您在IDE中键入最后一个字符之前花费的时间,而是在提交完成后可以再次开始键入的时间并不重要,而是您要等待多少时间。这就是为什么编译器应该快的原因。花费更多的时间来阅读和编写代码并不重要,因为这样做时,您不必等待,而在编译代码时,您很想切换到其他活动。
2014年

10
@托马斯:这不是关于分心,而是关于烦恼。同样,为100毫秒。对人们使用网站的方式具有“可衡量的影响”。此处的模式相同:100毫秒。与您观看YouTube视频或启动PC所花费的时间相比,这没什么了:有意识地,您不会注意到600毫秒之间的时差。和700毫秒。延迟。但是在不知不觉中,它会影响您的行为。以同样的方式,稍慢的提交会阻止您提早和频繁地提交。
2014年

41

让我成为不同意我的回答者的人。

这在TFS世界中被称为门控签到,我希望在其他地方也可以。当您尝试通过门控检入检入到分支时,将架子集发送到服务器,以确保您的更改得以构建并且指定的(读取:全部)单元测试通过。如果他们不这样做,它会通知您您是破坏了构建的坏猴子。如果是这样,更改将进入源代码管理(是!)。

以我的经验,门控检入是成功进行单元测试的最重要过程之一-进而扩展到软件质量。

为什么?

  • 因为封闭式检入迫使人们修复损坏的测试。一旦坏了的测试变成人们可以做而不是必须做的事情,懒惰的工程师和/或忙碌的商人不再优先考虑它们。
    • 测试被破坏的时间越长,修复起来就越困难(且成本更高)。
  • 因为一旦人们应该运行测试而不是必须运行测试,那么懒惰/健忘的工程师和/或急躁的商人就会绕开运行测试。
  • 因为一旦单元测试影响了您的提交时间,人们就会真正开始关心将其测试变为单元测试。速度很重要。重现性很重要。可靠性很重要。隔离很重要。

当然,您最初带来的好处是-当您完成检入门和一组可靠的测试时,每个变更集都是“稳定的”。您节省了“上一次构建良好时是什么时候?”的所有开销(以及潜在的错误)。-所有版本都足以对抗。

是的,构建和运行测试需要时间。以我的经验,一个大小合适的C#应用​​和5k单元测试需要5到10分钟。我不在乎。是的,人们应该经常入住。但是他们还应该经常更新任务,或者检查电子邮件,或者喝点咖啡或其他数十种“不使用代码”的事情,这些事情构成了软件工程师在那段时间的工作。签出错误的代码要比5到10分钟花费更多。


3
我想补充一下+1,许多开源项目在贡献和承诺之间有着明显的区别。这样做的原因与存在门禁值机的原因非常相似。
JensG 2014年

门控检入的一个重要问题是,它禁止解决难于解决的代码的协作问题。对于分布式团队来说,这一点更为重要。
CuriousRabbit 2014年

2
@CuriousRabbit-您如何看待?人们很少协作,即使他们的工作协同。否则,货架集或未注明日期的任务分支将对此有好处,同时又不妨碍团队的其他成员。
Telastyn 2014年

@ Telastyn– Shelvesets对我来说是个新名词。我认为这是MS VS选项,对于大量项目而言这不是选项。在移动应用程序开发领域,VS是非参与者。
CuriousRabbit 2014年

3
@CuriousRabbit-真的吗?分配了17个小时以上的团队关心的是等待10分钟的提交,而不是在冒犯的一方睡着时可能破坏构建?似乎……不尽人意。
Telastyn 2014年

40

提交应该运行得很快。当我提交一些代码时,我希望将其推送到服务器。我不想等待几分钟来进行一系列测试。我对我推送到服务器的内容负责,不需要任何人使用提交挂钩来照顾我。

就是说,一旦到达服务器,就应该对其进行分析,单元测试并立即(或在很短的时间内)构建。这会提醒我以下事实:单元测试已损坏,或者未编译,或者使静态分析工具显示出混乱。完成的速度越快(构建和分析),我的反馈就越快,并且我能够更快地解决它(思想并没有完全从我的大脑中移出)。

所以不,不要在客户端的提交钩子中放置测试等。如果有必要,请将它们放在提交后的服务器上(因为您没有CI服务器),或者放在CI构建服务器上,并在出现问题时适当地向我发出警报。但是不要一开始就阻止提交。

我还应该指出,在对“测试驱动开发”进行一些解释之后,应该检入中断的单元测试。这演示并记录了该错误的存在。然后,稍后的检入将是修复单元测试的代码。在单元测试通过之前阻止任何签入会降低签入未能记录问题的单元测试的有效值。

相关:我应该对已知缺陷进行单元测试吗?以及在失败的单元测试中进行检查的价值是什么?


2
Commits should run fast.这有什么好处?我很好奇,因为我们当前使用门控签入。通常我的签到工作是一个小时左右的时间,所以5分钟的等待并不重要。事实上,我也发现,其正常的时候,我在赶时间的验证构建在捕获愚蠢的错误非常有用(如冲的结果)
贾斯汀

1
@Justin无论在哪里,等待五分钟就是等待五分钟。一个不应该需要在每次你提交时间打出来的nerf剑。对于我来说,将一个小时左右的工作分解成多个提交,每个提交都是彼此的概念单元,这是很常见的-“提交其余服务代码”,然后“提交服务的客户页面的代码”作为两个单独的提交(更不用说将css调整作为另一个提交了)。如果其中每一个都花了5分钟才能运行,那么我将有1/6的时间花在等待测试上。这将导致较大的汇总提交,而更难以跟踪错误

5分钟等待运行单元测试?我觉得你们正在从事的项目应该分解为较小的部分。
user441521 '16

catching silly mistakes (as a result of rushing)完全是@Justin 。总的来说,匆匆忙忙是软件工程中的不良做法。罗伯特·C·马丁(Robert C. Martin)建议编写类似手术的代码youtube.com/watch?v=p0O1VVqRSK0
杰里·约瑟夫

10

原则上,我认为防止人们对主线进行更改以破坏构建是有意义的。也就是说,对存储库的主分支进行更改的过程应要求确保所有测试仍然通过。就浪费时间而言,中断构建成本太高,以至于项目上的所有工程师都无法做其他事情。

但是,提交挂钩的特定解决方案不是一个好的计划。

  1. 开发人员必须在提交时等待测试运行。如果开发人员必须在工作站上等待所有测试通过,那么您就浪费了宝贵的工程师时间。工程师必须能够继续执行下一个任务,即使由于测试失败而不得不退回去。
  2. 开发人员可能希望在分支中提交损坏的代码。在较大的任务中,代码的开发人员版本可能会花费大量时间而不处于通过状态。显然,将代码合并到主线中将是非常糟糕的。但是,相当重要的一点是,开发人员仍然可以使用版本控制来跟踪其进度。
  3. 偶尔有很好的理由跳过该过程并绕过测试。

2
通过允许开发人员签入个人分支或本地存储库,避免了#1的出现。只有当开发人员希望将他们的代码放在某个地方时,其他开发人员才能看到需要运行单元测试。与#1一样,#2仅通过钩接到主线分支而消除。#3被以下事实消除了:A)可以禁用任何此类功能,即使这很麻烦(并且应该是麻烦),并且B)可以禁用单个失败的单元测试。
布莱恩(Brian)

@Brian,我完全同意,您可以进行此工作。但是,尝试通过阻止提交挂钩在服务器端进行操作将无法正常工作。
Winston Ewert 2014年

好点。 Breaking the build is simply too costly in terms of lost time for all engineers on the project我建议使用某种构建通知工具来避免所有工程师最终在每次损坏的构建中浪费时间
Jerry Joseph

3

不,您不应该像其他答案所指出的那样。

如果您想要确保没有失败测试的代码库,则可以在功能分支上进行开发,并将请求拉入master。然后,您可以定义接受那些拉取请求的前提条件。好处是您可以真正快速地进行推送,并且测试在后台运行。


2

我必须等待成功的构建和对主分支的每次提交进行测试,这确实很糟糕,我认为每个人都对此表示同意。

但是还有其他方法可以实现一致的主分支。这是一个建议,在TFS中的门控签入方面有点类似,但是可以推广到具有分支的任何版本控制系统,尽管我将主要使用git术语:

  • 有一个暂存分支,您只能将其在开发分支和主分支之间提交合并

  • 设置一个挂钩,该挂钩可启动构建或将其排队并在暂存分支上对提交进行测试,但这不会使提交者等待

  • 在成功的构建和测试中,如果最新,则自动使主分支前进

    注意:不要自动合并到主分支中,因为经过测试的合并(如果不是从主分支的角度来看是向前合并),当合并到主分支中且提交之间存在提交时,可能会失败

作为结果:

  • 禁止人员在可能的情况下自动提交到主分支,如果存在漏洞或强制执行此操作在技术上不可行,也将作为正式流程的一部分

    至少,一旦这是基本原则,您就可以确保没有人会无意间或没有恶意。永远不要尝试这样做。

  • 您必须在以下两者之间进行选择:

    • 如果先前的尚未构建且未经测试的合并失败,则单个分段分支将使否则成功的合并实际上失败

      至少,您会知道哪个合并失败并需要有人纠正,但是对于进一步的构建和测试的结果,两者之间的合并是不容易被跟踪的(通过版本控制系统)。

      您可以查看文件注释(或责备),但是有时文件的更改(例如,配置)会在意外的地方生成错误。但是,这是相当罕见的事件。

    • 多个分段分支,这将允许无冲突的成功合并到达主分支

      即使在某些其他登台分支具有不冲突的合并失败的情况下。可追溯性要好一些,至少在一个合并没有预期到另一个合并带来影响的情况下。再次重申,这种情况很少见,不必每天或每周担心。

      要在大多数情况下进行无冲突的合并,重要的是明智地拆分分段分支,例如按团队,按层或按组件/项目/系统/解决方案(即使您命名)。

      如果同时将主分支转发到另一个合并,则必须再次合并。希望这不是非冲突合并或很少冲突的问题。

与封闭式签入相比,这样做的好处是您可以保证主分支正常工作,因为主分支只允许前进,而不会自动将更改与它们之间提交的内容合并。因此,第三点是本质区别。


但是,拥有一个正常工作的分支的目的(通常)不是为了确保您拥有一个稳定的版本,而是为了减少由于开发人员在同一代码中一起工作而造成的混乱。
Telastyn 2014年

如果您拥有遍布全球的大型团队的大型仓库,则不会。例如,Microsoft在Windows中使用了类似的,更分层的方法。
2014年

2

我更喜欢“通过单元测试”作为提交代码的大门。但是,要使其正常工作,您将需要做一些事情。

您将需要一个可缓存人工制品的构建框架。

您将需要一个测试框架来缓存(成功的)测试运行中的测试状态,并带有任何给定的伪像。

这样,通过单元测试的签入将很快(从源到开发人员在签入之前检查测试时构建的人工检查),将阻止那些失败的单元测试,并且方便地忘记在提交之前检查构建,因为编译和测试周期很长,因此鼓励下次记住这样做。


1

我要说的是,这取决于项目和在“提交”上运行的自动化测试的范围。

如果您希望在签入触发器中运行的测试确实非常快,或者如果开发人员工作流程无论如何都要在签入后强制执行某些管理工作,那么我认为这没什么大不了,而是迫使开发人员仅检查绝对可以运行最基本的测试的东西。(我假设您只会在这样的触发器上运行最基本的测试。)

而且我认为,在速度/工作流程允许的情况下,不要将更改推送给未通过测试的其他开发人员是一件好事-并且您仅知道运行它们是否失败。

您在问题中写出“ commit ... to remote branch”,这对我意味着这(a)开发人员每隔几分钟就不会做一次,因此稍作等待是可以接受的,并且(b)之后这样的提交,代码更改可能会影响其他开发人员,因此可能需要进行其他检查。

对于这样的操作,我可以同意“不要在等待时让您的开发人员打起大拇指”的其他答案。


1

不允许在trunk上执行损坏的提交,因为trunk可能会投入生产。因此,您需要确保在中继之前必须有一个网关需要通过。但是,只要回购不在主干中,就可以在回购中完全没问题。

另一方面,要求开发人员在将更改推送到存储库之前等待/修复问题有很多缺点。

一些例子:

  • 在TDD中,通常在开始实施功能之前提交并推送对新功能失败的测试
  • 通过提交并推送失败的测试来报告错误的方法也是如此
  • 推送不完整的代码可让2个或更多的人轻松地并行处理某个功能
  • 您的CI基础架构可能需要花费时间进行验证,但是开发人员不必等待
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.