在单元测试中检查文件内容/编码是否被认为是“不良做法”?


84

有一点上下文:今天早些时候,我不得不更新我的另一位同事提供的一些SQL代码,由于它是一个很大的脚本,因此将其存储为单独的文件(然后在运行时读取并执行)。在执行此操作时,我不小心重新引入了几个月前的两个错误,即:

  • 出于任何原因,ASCII文件都以UTF-16编码(同事通过电子邮件将其发送给我,这可能是导致该文件的原因)。
  • 该脚本缺少初始SET语句(由于生产中使用了某些驱动程序,而本地没有进行全新安装,因此该脚本是必需的)。

经过大约一个小时的调试之后,我决定编写一些单元测试以确保不再发生这种情况(并在断言消息中包含一种快速修复它的方法,以便于将来的开发人员轻松修复)。

但是,当我推送此代码时,另一个同事(也是我们团队的负责人)走到我面前,告诉我,我不应该再做这些事情,因为:

“这些东西不属于单元测试”

“单元测试仅应用于检查代码流”

我现在很矛盾,因为我仍然认为自己做的没错,因为以后不会再引入此错误,但是这位同事还是一名资深人士,最终可以决定我们花时间在。我该怎么办?我这样做不对吗?是否被认为是不良做法?



35
单元测试仅应用于检查代码流 ”“这是胡说八道。传统上,它们应包括所有必要的测试,以确保单独考虑的“单元”是正确的。如果您仅编写对“检查流程”有用的那些单元测试,那么无论如何,我希望您也有单独的扩展测试套件(由QA部门撰写?)。
gbr

8
无论如何,您同事的问题很可能就是您进行这些测试的地方。我将专注于此,不讨论教派/圣战。这些测试对于您添加到其中的套件而言可能太慢了,但是您的同事也完全有可能只专注于他的单元测试的想法,而从一个根本不存在的问题中提出了一个问题。因此最好先弄清楚真正的问题是什么。
gbr

2
顺便说一下,这些测试确实看起来像您每次修改该SQL文件时要运行的东西。这里的主要问题可能是测试工具,它可能不支持“仅在修改后才能运行”操作模式。这就引起了实际的,具体的问题,可能值得手动添加“仅在修改后”功能,并为这些特定测试添加一些麻烦。
gbr

5
取而代之的测试,该文件具有正确的内容和编码为什么不测试,它的工作原理
immibis

Answers:


156

您编写的测试很可能比单元测试更接近集成或回归测试。尽管这条线可能很模糊,有时会成为关于什么是单元测试或不是单元测试的棘手问题,但我还是回到您的同事那里,问您编写的测试应该在哪里,因为它们确实增加了价值,从而确保了代码的正确性。

我不会过多地关注什么是单元测试或什么是单元测试,并且意识到即使是集成测试,测试中仍然会有价值。


评论不作进一步讨论;此对话已转移至聊天
Thomas Owens

36

从技术上讲,它不是单元测试,而是更多的验证步骤。正确的方法实际上取决于您的工作流程需要。您的团队负责人对单元测试的目的是正确的。我的感觉是,这种情况下仍需要使用错误的工具来完成一项工作。因此,从此开始:

我要解决的问题是什么?

通过描述,您需要验证所有数据库脚本是否符合某些标准。

有哪些工具/过程可用来解决问题?

通常通过静态分析工具检查源代码的质量。如果您没有静态分析工具来验证SQL,则可以创建一个快速而肮脏的工具,该工具可以对传入的任何 SQL文件执行检查。检查是否有静态分析工具可以处理您正在讨论的问题,这没有什么坏处。

如果您将其作为构建基础结构的一部分,例如将其合并到Jenkins中或类似的东西,则可以将其应用于项目中的所有SQL文件。

单元测试只能解决当前文件的问题。

如何传达对工具的需求?

这很简单,您可以咨询团队负责人。他可以与产品负责人以及您一起确定投资工具的风险/回报。如果这可能是一次性的问题,那么该工具可能会过大。如果解决最大问题的工具很容易,那么仅进行健全性检查就值得。

您的团队负责人可能有一些您(或我)没有考虑过的想法,可以更正确地解决问题。


21
在讨论成本与风险的关系时,请务必注意,由于重新引入了先前解决的错误,它已经造成了时间的浪费。仅此一项就可以使验证自动化。+1
jpmc26,2017年

1
@ jpmc26,我完全同意。讨论应该从以下事实开始:您花了很多小时才弄清楚问题出在哪里,而单元测试是您防止团队其他成员损失相同时间的第一个尝试。但是,通常与与负责管理和利益相关者的团队负责人合作很受赞赏。作为团队负责人,我希望能够捍卫我们管理的工具/实践/代码。如果我看到无法追溯到实际需求的单元测试,我也会很担心。
Berin Loritsch '17

1
@BerinLoritsch 从技术上讲,这不是单元测试,我真的很想知道您基于此断言使用的是什么“技术”。据我所知,对单元测试没有一个权威的定义,每个人对“ 它们是什么”都有自己的看法。
gbr

2
@gbr开发人员之间有一个非正式协议,即单元测试是独立于外部系统运行的测试。他们测试代码本身,而不测试与文件,数据库或其他外部服务的交互。Wikipedia确实记录了这种理解:en.wikipedia.org/wiki/Unit_testing#External_work
jpmc26

1
@BerinLoritsch还有可能我们都以不同的方式解释了这个问题,无论如何,它不是很详细,作者还没有找任何人。无论如何,我对进一步讨论这些测试的分类不是很感兴趣,重要的是它们是否应该存在(我很确定它们应该存在)以及运行它们的频率(在IDE的每次更改中,每次本地提交,每次推送到中央存储库,每次都这样)。
gbr

19

将访问文件的测试称为“单元测试”是不好的做法。

他:“这些东西不属于单元测试”

您:“说得通,但是我找不到放置它们的更好的地方。它们属于哪里?”

不幸的是,存在哪些测试以及如何组织这些测试完全取决于公司。因此,您需要了解贵公司如何处理这些测试。

如果除单元测试之外,您还没有其他方法可以运行自动化测试,那么实用的方法是使用前缀将实际上不是单元测试的单元测试标记为前缀,直到您有足够的能力来开始找出哪种类型的单元测试为止。您实际需要的测试。之后,您可以开始整理。


2
将访问文件的测试称为“单元测试”是不好的做法。这里正在访问的文件是文件。它将像访问任何源文件一样进行访问(以对其进行解析)。测试可能不会以与被检查的“单元”(SQL)相同的语言进行,这一事实使它不合常规,但不应影响其作为单元测试的分类。继续...
gbr

1
...实际上,文件的正确编码是任何编译器每次读取源文件时都进行的“测试”。这里的问题是,作为运行时解释的外部文件,“编译器测试”将不会自动运行,因此完全将它们显式添加是完全合适的,我认为可以将其视为该文件的“单元测试” SQL代码段。将它包含在每次修改文件时运行的(潜在)测试套件中似乎是合理的。
gbr

3
顺便说一句,一般建议不要访问外部文件,只要外部文件可以用模拟或类似的东西代替。而且,按照大多数定义,单元测试可以访问外部文件或任何其他内容,强烈建议不要使用它,因为它会大大降低速度。商店可以自由地规定,您不能将访问文件的测试添加到运行最频繁的测试套件中,但这不会使此类测试不值得“单元测试”的称号,它们只是使它们“不”放在该项目最常运行的测试套件中”。
gbr

2
@Warbo (通常)访问文件一个坏习惯,(最重要的)原因是,如果涉及“通过不稳定的NFS链接读取的GB”,则它们并不慢;如果引用例如Michael Feathers,它们就很慢。 ,它们需要1/10秒的时间。这是因为您希望尽可能频繁地运行测试,理想情况下是在IDE中进行的每一次更改,并且当您拥有很多测试时(如您应有的那样),甚至十分之几秒就可以累积到几个小时。(续...)
gbr

1
@Warbo ..就是说,重要的是总花费的时间,因此,如果您有一个很小的项目,而且您确定自己将保持很小的时间,则可以对单个测试的速度更加宽容。而且,如果您真的不希望经常运行它们,则可以完全自由地让他们打电话给OPMROC员工,以从柜子中获取并传真文件。您还可以选择在测试很少的情况下放松,然后在测试开始花费过多时返回加速测试,但是您必须考虑到这是您积累的债务。
gbr

14

迈克尔·费瑟斯(Michael Feathers)在他的《有效处理旧版代码》一书中说过:

在行业中,人们经常来回讨论特定测试是否为单元测试。[...]我回到两种品质:测试运行速度快吗?它可以帮助我们快速定位错误吗?

您的测试是否可以帮助您快速定位错误并快速运行?如果是,那就去做!如果没有,那就不要!就这么简单!

话虽如此,您正在与其他人一起工作的环境中,必须与他们相处。即使您私下不同意,您也可能不得不以自己的方式来做。


这是一个很好的经验法则,但是如果他写了“ 是否在最常运行的测试套件中添加特定的测试 ”而不是弄乱“单元测试”这个术语,他将避免困惑。
gbr

1
@gbr如果测试结果准确,那么唯一重要的事情就是测试运行的速度。如果我有100个测试,每个测试的运行时间都在0.1秒以内,那么总共运行的时间将少于10秒。我很高兴经常运行它们。如果我有1000个测试,则最多需要1分40秒。太长了,我不会经常运行它们,但是一旦使用较小的一组100个测试进行更改,我将立即运行它们。我不在乎这在技术上是验收测试还是其他。如果可以帮助我更快地发现错误,则无论语义如何,我都会做到。测试仅在运行时提供价值。
CJ Dennis

我大都同意(例如,独立性是另一项非常重要的事情),但是,如果Michael Feathers不对“单元测试”的含义感到困惑,那会更好。并不是他应该为这种混乱负责(在我到目前为止浏览的部分中,他的书非常出色)。无论如何,我只说了一点点。
gbr

@gbr他没有重新定义单元测试,他说那不应该成为您包含的标准。您的标准应该是有用的,这就是他的定义。
CJ Dennis

我重新阅读了该部分;我不确定,尚不清楚这是一个(某种)定义还是一个准则。但是实际上“ 它能帮助我们快速定位错误 ”可能很好地暗示了他之前所说的关于隔离等的内容。因此,我可能什么都没做(尽管您的回答仍然可能会被误解)
gbr

10

我有时针对源代码文件,配置文件等编写了类似的测试。我不会称其为单元测试,因为(a)它们正在访问文件系统并且可能不是超快的(b)我不在乎是否在每次签入时都执行它们(相对于每晚CI服务器)。

您可以将它们称为集成测试;当然,它们比单元测试更接近于这种观点。

我对它们的术语是资源测试。恕我直言,如果它们在CI服务器上每晚执行,则完全有道理:成本最低,并且,如果明智地使用,显然会增加价值。一个明智的定义:如果测试正在检查引起问题的问题(例如您提到的编码)。


4

单元测试就是关于测试方法或代码“单元”的全部。您正在测试软件中最小的一组逻辑和代码。

稍后,当您将其与其他单元一起加入时,您将执行集成测试。

我希望您的团队领导鼓励您的主动性,并且应该提出其他建议。您绝对有正确的想法。

您的SQL就像其他任何较低代的语言(如C#或Java)一样,都是代码,因此应经过测试。验证和确认属于所有测试级别。因此,包含了编码和SET语句,但不一定要进行独家测试。诸如行尾或括起来的常规内容通常只能使用SCM挂钩或功能。

最佳实践是进行回归测试,以确保不会再次引入过去的错误。通常,将与错误的任何解决方案一起创建测试。如果这些错误未在单元/集成或系统级别的回归测试中涵盖,然后重新引入,则是团队问题,过程问题,而不是单个问题。

问题是...语法错误,“单元”中缺少语句或逻辑块通常不会得到测试。您正在以不同组合测试设备的输入和输出,并测试可能产生的多种可能性。

回到丢失的SET语句-它们有助于告知输入和输出进行测试的多种可能性。如果缺少任何选定的SET,您会写什么测试失败?


“代码单元”测试是单元测试的一种方法。以我的经验,这会导致脆弱的测试和巨大的膨胀(例如过度嘲笑)。IMHO更好的另一种单元测试方法是“功能单元”。某些功能(例如,“登录时设置cookie”)是否需要一种方法或十二个相互通信的过程并不重要,它仍然是一个单元。
华宝(Warbo)'17年

@Warbo-我将其称为(而不是)集成测试。单元测试不需要过多或大量的东西。单元测试应该小而快速。实际上,按功能进行测试会导致您所描述的内容。易碎测试是1.比预期更大或更大的测试。2.高度耦合3.没有单一责任。
罗斯

3

如果您的文件成为产品的一部分,则其内容必须正确。没有理由不验证这一点。例如,如果您在某个文件夹中需要六个1024x 1024图像,则一定要编写一个单元测试来检查您是否具有正确的图像。

但是您可能不仅拥有这些文件,还拥有一些读取文件的代码。您可以为该代码编写单元测试。在上面的示例中,读取六个图像之一的函数是否在内存中返回1024 x 1024图像(或应该产生的图像)。

无论如何,它可能不是单元测试,但它是有用的测试。而且,如果您使用允许您执行有用测试的单元测试框架(不是单元测试),为什么不使用单元测试框架呢?


2

也许我误解了您的问题,但是对我来说,这听起来像是一个问题,不需要任何形式的专用测试即可捕获,而只需通过版本控制系统即可捕获。对代码库的任何更改都应在提交前逐个补丁进行审查。在git中执行此操作的一种简单方法是添加更改

git add -p

对于文本文件中的每个更改,工作目录都会询问您是否真的要保留它。例如,这将使您看到那些“初始SET语句” 的删除。

如果整个文件的编码发生更改,则可能会有所不同:算法将无法区分新旧文件,因此git add -p根本不会添加任何内容。这将在我进行任何提交之前执行的其他命令中可见,即

git status

在这里,你会看到以红色突出显示的文件,表明有变化。调查这些原因为何没有git add -p迅速解决,这将使问题显而易见。


祈祷告诉,这如何帮助任何未来的开发人员避免完全相同的问题?...关于自动化测试(对于断言和按合同设计也有效)的事情是,它们是自动化的
vaxquis

@vaxquis确实避免了完全相同的问题-尽管有些偶然,但作为工作流程的副作用,由于不同的原因,这是一个好主意。我的意思是,这个问题完全可能发生,这表明OP的团队没有很好地利用他们的VCS。—不反对自动化测试,但是它们的价值在于测试语义属性,这些语义属性可能会因无害更改程序逻辑而中断。并不是要检查源代码更改的所有可能愚蠢方式。
大约

根据您的逻辑,我们不需要安全带;我们只需要更加谨慎地驾驶并减少事故的发生...您错过了OP提出的要点- 人为错误的要点。没有任何数量的VCS可以保护您免受此伤害。同样,FWIW:如果测试可以自动化,则应该自动化。人永远是工程过程中最薄弱的环节。git就是最好的例子-一个伟大的工具,但对于凡人来说几乎是无法使用的
vaxquis

@vaxquis不,不!安全带类似于有意义的测试:它们会遇到很多种可能偶然发生的情况。对文件编码的测试类似于一个机器人,它将跟踪您并阻止您通过将豆塞满鼻子来窒息自己。
大约

根据OP,错误格式的文件已经发生过两次,因此显然可能是偶然发生的。
gnasher729

1

需要考虑的另一个角度:由于这两个条件是程序运行的要求,因此您是否不应该将逻辑嵌入到执行逻辑附近?我的意思是:您在读取文件和/或验证其内容之前先测试文件的存在,对吗?那么这有何不同?我认为,由于这是代码外部资源,因此应在实际使用之前在运行时对其进行验证。结果:更强大的应用程序,无需编写其他测试。


1
仅在运行时失败才能使它成为更强大的应用程序?当然,也可以在问题的根源附近进行运行时检查,但是如果您可以在错误导致运行时问题之前检测出错误,那就更好了,您不是吗?您确定您熟悉自动测试吗?
gbr

1
“只有在运行时失败才能使它成为更强大的应用程序?” 如果检查失败,则引发异常。在您的测试中,只需检查要测试的代码部分是否返回了预期的结果:这消除了检查另一个失败原因的负担。
Gianluigi Zane Zanettini博士

1
通常,您的策略(几乎)与单元测试和自动化测试无关,这是不同的,具有不同的用途。
gbr

1
我打算建议这个。该错误是实现细节。我想如果躲闪的编码是在私有字段而不是独立文件中,则响应会大不相同!听起来OP有两个问题:资源文件可能编码错误,生产对开发人员的行为有所不同。通过在运行时检查文件,就在使用它之前,我们解决了第二个问题:dev和prod将引发相同的错误。然后,单元测试可以专注于实际功能而不是实现细节。这些“内部”检查将像私有方法一样执行。
华宝(Warbo)'17年

1
@ Dr.GianluigiZaneZanettini Bah ...我放弃了...据我所知,充其量,在您的评论“澄清”之后,您的回答充其量是题外话(不是问题的答案),但实际上,就目前而言,这是完全错误的!无需编写其他测试???我没有足够的声誉来拒绝投票,但请考虑一下我是否做到了。而且我认为继续进行此次对话没有太大价值。
gbr

1

测试与任何其他代码都是相同的代码,并且如果足够复杂,也可以从...单元测试中受益。将此类前提条件检查直接添加到测试中似乎最简单。

大多数测试足够简单,不需要这样做,但是如果其中一些测试足够复杂,那么这些前提条件检查根本看不出任何根本性的错误。当然,没有它们,测试也应该失败,但是一个好的单元测试也可以告诉哪个单元失败了。

用作测试一部分且必须具有一定内容和编码的脚本可能是一个单元。它可能比其余测试具有更多的代码和逻辑。使用这种脚本进行的测试不是有史以来最好的设计,并且如果可能的话,应该将其重构为更直接的内容(除非这是集成测试)。


1
作者没有在任何地方说SQL脚本是某些测试的一部分,您似乎误解了该问题
gbr

很难理解,我假设SQL脚本是测试的一部分。
h22

您的评论 “那里很难理解” ...
gbr

很难理解。否决这个问题。
h22

1

首先-测试的目的之一是防止代码中再次出现问题-因此,您绝对应该继续编写这种性质的测试。

其次-命名很难。是的,这些显然不是“单元测试”,但是它们可能是构建过程中理想的和必要的部分,因为它们可以保护您免受明显的错误的影响,并且因为它们可以更快地为您提供有关错误的反馈(特别是鉴于您看不到错误)。对开发人员的影响)。

因此,问题的实质(应该在您的上下文中)更多地是关于何时以及如何运行这些测试,而不是它们是什么。

过去,我已经广泛使用这种测试-他们为我们节省了很多痛苦。


如果有人愿意解释不赞成票,我将不胜感激
Murph

1

单元测试是关于独立执行一个代码单元,以确认它针对正确的输入产生了正确的结果。隔离应该使被测单元和测试本身都具有可重复性,即不应依赖或引入副作用。

SQL并非完全可以单独进行测试,因此任何SQL测试都不完全是单元测试,并且除SELECT语句外几乎可以肯定会产生副作用。我们可以称其为集成测试而不是单元测试。

始终明智的做法是确保在开发周期中尽早发现可能引入的任何缺陷,并且这样做的好处是易于识别缺陷的来源,从而可以迅速发现缺陷。更正的。

有问题的测试可以更适当地从“单元测试”的主体中移出并放置在其他位置,但是如果它们正在做一些有用的事情,例如防止可能会引入几个小时的缺陷,那么就不应将其完全删除。下。

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.