单元测试真的有用吗?[关闭]


98

我刚刚获得CS学位,并且目前有初级.NET开发人员(C#,ASP.NET和Web表单)的工作。回到我上大学时,单元测试的主题确实涵盖了,但是我从来没有真正看到它的好处。我了解应该执行的操作,即确定代码块是否适合使用。但是,我之前从未真正编写过单元测试,也从未感到有必要这样做。

正如我已经提到的,我通常使用ASP.NET Web表单进行开发,最近我一直在考虑编写一些单元测试。但是我对此有一些疑问。

我读过单元测试通常是通过写“模拟”来进行的。虽然我理解了这个概念,但似乎无法弄清楚如何为完全动态的网站编写模拟文件,并且几乎所有内容都依赖于来自数据库的数据。例如:我使用很多具有ItemDataBound事件等的转发器(同样取决于“未知”数据)。

因此,问题1:是否经常为ASP.NET Web编写单元测试表单?如果是,那么:如何解决“动态环境”问题?

在开发时,我会经历很多反复的尝试。这并不意味着我不知道自己在做什么,而是我通常会写一些代码,点击Ctrl F5查看会发生什么。尽管这种方法在大多数情况下都能奏效,但有时我会觉得自己有些笨拙(由于我的经验不足)。有时我也会像这样浪费很多时间。

因此,问题2:你们会建议我开始编写单元测试吗?我认为这可能对我的实际实施有所帮助,但是我再次觉得这可能会使我慢下来。


1
我只想说这另一面是一些需要他们作为实践的地方。我现在在一个地方,除非单元测试中包含代码,否则我们不允许检入。因此,需要注意的一点是,即使您可能不相信它们,也可能会要求您沿途进行。我认为这是一种有用的技能,可以在您的武器库中学习和使用。我常常会从测试中发现我的代码中的小错误和不幸,几乎是与自己进行一些QA会话。
亚当

9
没有编写任何单元测试,您如何准确地涵盖单元测试?这是“给自己定级”还是“设计自己的课程”学校之一?
JeffO

4
单元测试是有问题的-非常适合动态语言,使用的语言越严格,用处就越小,但是请务必进行构建测试。它们不必是单元测试,但是您实际上应该具有可以与JUnit一起运行的集成测试,这非常有用。
Bill K

13
-1对不起,即使我不同意我的观点,我也全力以赴,但这是错误的。没有人声称“单元测试用于捕获新代码中的错误”,因此这是一个稻草人单元测试用于捕获回归。Dijkstra的报价是在上下文之外进行的-上下文是,如果您对问题有正式的规范,则测试是没有意义的。
sleske 2012年

9
“单元测试用于捕获回归”。否。自动化测试用于捕获回归。回归总是要求相同的测试运行数百次,因此值得进行自动化测试。不幸的是,有关此问题的许多答复和评论实际上都在处理“自动化测试是否有用?”这一问题。单元测试可能是自动化测试的一种形式,但是它们的重点完全不同。我当然认为自动化测试值得称赞,但不应作为论证单元测试(或TDD)的理由。
njr101 2012年

Answers:


124

我认为:是的,是的,应该的。

  1. 他们使您对所做的更改充满信心(其他所有操作仍在进行)。这种信心是塑造代码所需要的,否则您可能会害怕更改。

  2. 它们使您的代码更好;大多数简单的错误都在单元测试中被发现。尽早地捕获错误并进行修复通常比以后进行修复(例如,在应用程序处于生产中)便宜。

  3. 它们充当其他开发人员的文档,说明您的代码如何工作以及如何使用它。

您面临的第一个问题是ASP.NET本身并不能帮助您编写单元测试-实际上它对您不利。如果有任何选择,请开始使用ASP.NET MVC,它是在考虑单元测试的情况下创建的。如果不能使用ASP.NET MVC,则应在ASP.NET中使用MVP模式,以便至少可以轻松地对逻辑进行单元测试。

除此之外,您只需要精通编写单元测试。如果您练习TDD,则会创建可测试的代码,换句话说,代码很干净。

我建议您练习并配对程序。在读的时候:

或者,首先概述:


4
刚开始进行单元测试,我必须同意。与TDD方法相结合,它不仅是一种好习惯,非常有用,而且可以减少大量时间。我不认为即使添加新功能或修复错误后,我也无法运行单元测试并验证一切是否正常,我的项目还是没有用。我无法想到以其他任何方式进行回归测试。
kwelch 2012年

21
如果单元测试用作文档,则有问题。阅读500行代码以了解5行代码是如何工作的。
编码器

4
@Coder:当您测试更高级别的方法时,它确实涉及超过5行代码。

7
@coder:一个类的文档告诉您该类实例提供的服务。它告诉您有关在较大上下文中如何使用此类实例的信息,即对象之间的交互的信息较少。在某些情况下,测试为您提供了典型的交互代码,这是非常宝贵的起点,因此很多时候我都无法数清。
Stefano Borini,2012年

21
@Coder:它不记录什么的代码呢,它记录了假设在该代码所固有的,即为什么它应该工作。如果通过更改SUT使基本假设无效,则一个或多个单元测试将失败。这并不能替代更高级别的设计/体系结构文档,甚至XML文档,但它涵盖了那些东西永远无法做到的事情。
亚伦诺特,2012年

95

没有。

单元测试背后的概念基于一个前提,即自从发明单元测试以来就一直认为它是错误的:测试可以证明您的代码正确的想法。

拥有大量全部通过的测试只能证明一件事和一件事:您拥有大量全部通过的测试。它不能证明测试的内容符合规范。它不能证明您的代码没有编写测试时从未考虑过的错误。(而且您认为要测试的事情是您正在关注的可能问题,因此无论如何您都可能正确解决了这些问题!)最后但并非最不重要的一点是,它并不能证明测试本身就是代码,没有错误。(遵循最后一个逻辑结论,您最终会遇到乌龟。)

吉克斯特拉(Djikstra)早在1988年就放弃了“ 测试作为正确性证明” 的概念,而他所写的内容在今天仍然有效:

自从有人指出程序测试可以令人信服地表明存在错误,但是永远不能证明它们不存在以来,已经过去了二十年。在认真引用了这一广为人知的言论后,软件工程师回到了日常工作中,继续完善他的测试策略,就像昔日的炼金术士一样,继续完善他的金相纯化。

单元测试的另一个问题是,它会在您的代码和测试套件之间建立紧密的耦合。当您更改代码时,您希望会出现一些会破坏某些测试的错误。但是,如果由于需求本身已发生更改而要更改代码,则将导致很多失败的测试,并且必须手动检查每个测试并确定测试是否仍然有效。(而且,这也是可能的,虽然不常见,即现有的测试应该是无效的还是会通过,因为你忘了改变的东西,需要改变。)

单元测试只是一长串开发风潮中的最新技术,它们有望使编写工作代码变得更容易,而实际上并没有成为一个好的程序员。他们中没有一个人兑现了自己的诺言,也没有做到。真正知道如何编写工作代码根本没有捷径。

在稳定性和可靠性至关重要的情况下,有一些关于自动测试真正有用的报告。例如,SQLite数据库项目。但是对于大多数项目而言,要达到其可靠性水平是非常不经济的:测试与实际SQLite代码的比率几乎为1200:1。大多数项目都负担不起,而且无论如何也不需要它。


69
证明您的代码对于那些测试而言行为正确。如果你问我,那真是令人惊讶。
Stefano Borini 2012年

111
今天谁真正相信单元测试是“正确性证明”?没有人应该这么认为。您是正确的,他们只证明那些测试通过了,但这比编写单元测试之前多了一个数据点。您需要在许多不同的层进行测试,以使您自己了解代码质量。单元测试不能证明您的代码没有缺陷,但是它们确实提高了您的信心(或应该……),使您相信代码可以执行您设计的工作,并且明天将继续执行今天的工作。
Bryan Oakley

40
> but if the test itself is buggy, then you have false confidence并且如果手动测试仪不能正确执行其工作,则您也会有错误的信心。
Stefano Borini 2012年

33
@MasonWheeler:对不起,Mason,我不相信。在超过二十年的编程中,我认为我个人从未见过这样的情况:测试给人一种错误的安全感。也许其中一些确实做到了,我们已经修复了,但是这些单元测试的成本绝没有超过拥有它们的巨大收益。如果您不编写单元级别的代码就能够编写代码,那给我留下了深刻的印象,但是绝大多数程序员无法始终如一地做到这一点。
布莱恩·奥克利

41
如果您编写了通过代码的错误测试,则代码也可能也存在错误。编写错误代码错误测试的几率比仅错误代码少得多。单元测试不是万能的。它们是一个很好的额外层(谨慎使用),可以减轻手动测试人员的负担。
Telastyn

60

如果您曾经看到编写一种主要方法来测试学校的一小段代码的好处,那么单元测试就是该实践的专业版/企业版。

还要想象一下构建代码,启动本地Web服务器,浏览到相关页面,输入数据或将输入设置为适当的测试种子,提交表单以及分析结果的开销……与构建并点击nUnit运行按钮。

这也是一个有趣的形象... 在此处输入图片说明

我在这里找到了这张图片:http :
//www.howtogeek.com/102420/geeks-versus-non-geeks-when-doing-repetitive-tasks-funny-chart/


2
是的你可以。在单元测试中隔离Web层以测试Web配置(URLS,输入验证等)。通过对Web层和数据库层进行存根,可以在没有数据库或Web服务器的情况下测试业务层。我使用dbunit测试我的数据库。如果您愿意,仍然可以进行完整的集成测试,但是我将其作为开发后的特定任务来进行。
Heath Lilley

12
可爱的图形,但比例尺严重错误。正如我在回答中指出的那样,SQLite的完整测试覆盖率要求测试中的代码比产品本身中的实际代码多大约1200倍。即使您对全覆盖并不那么着迷,您仍然需要比产品多几倍的测试才能达到测试中任何有用的覆盖水平。为了在此处准确无误,该红线的垂直部分必须不断向上移动并持续大约3页的长度。
梅森惠勒2012年

27
@MasonWheeler这是一匹非常好的马,我想它可能已经死了……SQLite有很多测试,因为它的数据库真该死。我想确保可以。再举一个例子,Silverstripe框架的tests:code比率接近1:1,因此SQLite并不具有代表性。
2012年

15
@MasonWheeler您在芝诺悖论的第一个失败了。如果您想将该图像放大到SQLite的测试级别,则X轴还必须扩展到其当前长度的100倍左右。
伊兹卡塔2012年

2
当您是一个后端开发人员而又没有时间开始制作黑白网页,以便可以测试后端逻辑时,尤其如此。我所要做的只是提供模拟请求和会话对象,并通过单元测试运行我的控制器,最后,我知道它是对还是错。如果使用DI,则只需注入一个与内存数据库交互的DAO,以使您的数据库不会发生变化,或者简单地抛出a Exception并捕获它,以便不会提交数据。我对单元测试说是。
Varun Achar

49

如今,单元测试对此有些神秘。人们将其视为100%的测试覆盖率是一个圣杯,并且将单元测试视为开发软件的唯一途径。

他们错过了重点。

单元测试不是答案。 测试是。

现在,每当讨论出现时,都会有人(甚至是我)大声疾呼Dijkstra的名言:“程序测试可以演示错误的存在,但从不演示错误的存在。” Dijkstra是正确的:测试不足以证明软件能够按预期工作。但是这是必要的:在某种程度上,必须有可能证明软件正在按照您想要的方式工作。

许多人手工测试。即使是坚定的TDD爱好者也可以进行手动测试,尽管有时他们会拒绝这样做。它无济于事:就在您进入会议室将软件演示给客户/老板/投资人/等人之前,您将手动操作以确保其能正常工作。这没什么不对,事实上,即使没有100%的单元测试覆盖率和对测试最大的信心,只是期望一切都能顺利进行而无需手动运行(也就是测试),这将是疯狂的事情。

但手动测试,即使它是必要为构建软件,很少足够。为什么?因为手动测试是繁琐且耗时的,并且由人工执行。而且,众所周知,人类在执行乏味且耗时的任务方面很糟糕:他们尽可能避免执行这些任务,并且在被迫执行任务时往往做得不好。

另一方面,机器在执行乏味且耗时的任务方面非常出色。毕竟,这就是发明计算机的目的。

因此测试至关重要,而自动化测试是确保测试始终如一的唯一明智的方法。随着软件的开发,测试和重新测试很重要。这里的另一个答案指出了回归测试的重要性。由于软件系统的复杂性,对系统某一部分的频繁看似无害的更改会导致系统其他部分的意外更改(即错误)。没有某种形式的测试,您将无法发现这些意外的更改。而且,如果您想获得有关测试的可靠数据,则必须以系统的方式执行测试,这意味着您必须拥有某种自动化的测试系统。

这与单元测试有什么关系?好吧,由于其性质,单元测试是由机器而不是人工来运行的。因此,许多人误以为自动化测试等于单元测试。但这不是事实:单元测试只是超小型的自动化测试。

现在,超小型自动化测试的价值是什么?优势在于它们可以隔离测试软件系统的组件,从而可以更精确地确定测试目标,并有助于调试。但是单元测试并不是本质上意味着更高质量的测试。由于它以更精细的级别涵盖软件,因此通常会导致更高质量的测试。但是可以完全测试整个系统的行为,而不能测试其组合部件的行为,并且仍然可以对它进行彻底的测试。

但是,即使具有100%的单元测试覆盖率,仍可能无法对系统进行彻底的测试。因为单个组件可以孤立地完美工作,但是一起使用时仍然会失败。因此,单元测试虽然非常有用,但不足以确保软件按预期运行。实际上,许多开发人员通过自动集成测试,自动功能测试和手动测试来补充单元测试。

如果您没有在单元测试中看到价值,那么最好的入门方法是使用另一种自动化测试。在Web环境中,使用诸如Selenium之类的浏览器自动化测试工具通常会以较小的投资获得巨大的成功。将脚趾浸入水中后,您将更容易看到自动化测试的帮助。而且,一旦有了自动化测试,单元测试就变得更加有意义,因为它可以提供比大型集成或端到端测试更快的周转时间,因为您可以将测试仅针对当前正在使用的组件进行测试。

TL; DR:不必担心单元测试。只需担心先测试软件即可。


单元测试也是由人类编写的,可能是错误的。
Seun Osewa

41

这取决于

在软件开发方面,您会看到很多答案,但是单元测试的实用性实际上取决于编写的质量。标称数量的单元测试非常有用,它可以检查应用程序的功能以进行回归测试。但是,检查应用程序返回的大量简单测试可能毫无用处,并且提供了错误的安全感,因为应用程序“具有大量的单元测试”。

此外,您作为开发人员的时间很宝贵,花在编写单元测试上的时间就是在写新功能上所花费的时间。同样,这并不是说您不应该编写单元测试,但是每个公共功能都需要单元测试吗?我认为答案是否定的。执行用户输入验证的代码是否需要单元测试?由于它是应用程序中的常见故障点,因此很有可能。

这是经验发挥作用的领域之一,随着时间的推移,您会认识到可以从单元测试中受益的应用程序部分,但是要达到这一点还需要一段时间。


这是个好的观点。您必须知道如何编写良好的测试来捕获SUT的价值。
安迪

3
这基本上涵盖了如何教我进行单元测试的方法-先编写测试以涵盖最重要/易碎的情况,然后在假设错误的情况下添加更多测试(我认为某些事情“永远不会失败”,但确实如此) 。
布伦丹·朗

38

大学课程完成的项目与您在工作中编写的业务应用程序有很大的不同。区别在于寿命。大学计划“居住”多长时间?在大多数情况下,它在您编写第一行代码时开始,并在获得标记时结束。您可以说,实际上,它仅在实施时有效。“发布”通常等于其“死亡”。

为企业开发的软件超出了大学项目的发布时间,并且只要企业需要,它就可以继续存在。这很长。在谈论金钱时,没有人会花一分钱来拥有“更酷更整洁的代码”。如果25年前用C编写的软件仍然可以运行并且足够好(据其业务所有者的需求可以理解),则应要求维护它(添加新功能,改进旧功能- 更改源代码)。

我们得出一个非常重要的观点- 回归。在我工作的地方,我们有两个团队维护两个应用程序。一个,写了周围5 - 6年前用很少的代码测试覆盖率*,和第二个,第一个应用程序的更新版本,全面的测试套件(单元,集成并没有什么别的)。两个团队都有专门的手动(人类)测试人员。是否想为一线团队引入一个相当基本的新功能需要多长时间?3至4周。这段时间的一半是“检查其他一切是否仍然有效”。对于手动测试人员来说,这是繁忙的时间。电话响了,人们不高兴,有些东西又坏了。第二小组通常会在不到3天的时间内处理此类问题。

我绝不是说单元测试会使您的代码容易出错,更正确或其他您能想到的花哨的词。他们都没有使虫子神奇地消失。但是,当与自动化软件测试的其他方法结合使用时,它们使您的应用程序比以前具有更高的可维护性。这是一个巨大的胜利。

最后但并非最不重要的一点是,Brian的评论,我认为这是整个问题的重点:

(...)他们确实提高了您的信心(或应该……),使您的代码符合您设计的目的,并且明天继续做今天的工作。

因为在今天和明天之间,可能会有人做一些微小的更改,这将导致非常重要的报表生成器代码崩溃。通过测试,您可能会发现在客户对您有所帮助之前发现这一点的可能性。

*他们确实将越来越多的测试引入他们的代码库,但是我们都知道这些东西通常看起来如何。


10
+1000用于链接到Software_Regression。大学将单元测试暴露为您必须出于纯粹的信念而必须做的事情,而没有详细解释存在一种需要预防和控制的疾病,这种疾病称为回归。(然后存在回归测试与单元测试完全不同的问题,因为我发现唯一能很好解释它的读物是本书的免费样本
ZJR 2012年

25

是否正在为ASP.NET Webform编写单元测试,这是经常要做的事情,如果是,那么:我如何解决“动态环境”问题?

它并不经常执行。使用UI元素并不是单元测试所擅长的,因为没有很好的方法来以编程方式验证正确的事物最终出现在屏幕上的正确位置。

你们会建议我开始编写单元测试吗?我认为这可能对我的实际实施有所帮助,但是我再次觉得这可能会使我慢下来。

如果适用,是的。它们对于以可重复的方式验证特定案例非常有帮助。它们有助于抵御麻烦的更改,并在进行更好的更改时起到故障保护的作用。

要注意的一件事是它们通常会使您减速。

没关系

现在花一点时间(如果做得好的话)将在将来节省您的时间,因为它们会捕获一些错误,防止某些错误的再次发生,并使您更愿意在代码中进行其他改进以防止其腐烂。


8
+1用于实际回答问题的用例!其他所有人都跳到“单元测试”部分,而忘记了“ ASP.NET Web表单”部分……
Izkata 2012年

1
这是一个很好的答案,值得更多的批评,原因是因为您已经解决了问题,并且诚实地评估了放慢开发人员的速度,并且不保证会捕获或阻止每个错误,这是完全正确的。完全正确。
Mantorok

11

我刚刚获得了CS学位,并且目前从事初级.NET开发人员的工作(使用C#和通常的ASP.NET,Web表单,而不是ASP.NET MVC)。回到我上大学时,单元测试的主题确实涵盖了,但是我从来没有真正看到它的好处。

那是因为你从来没有编程过大。

我了解它应该做什么-确定是否要使用代码块>适合使用-但我之前从未真正编写过单元测试。(也>我是否曾经感到过需要..)

编写单元测试会强制您的代码符合可测试,有据可查且可靠的给定思维结构。它远远超出您的要求。

-我读过单元测试通常是通过写“模拟”来进行的。虽然我了解这个概念,但似乎无法弄清楚我应该如何为完全动态的网站编写模拟文件,并且几乎所有内容都依赖于来自数据库的数据。

使用模拟类模拟数据库访问,该模拟类返回填充有定义明确的硬编码数据的模型类。,用夹具数据填充测试数据库,然后从那里开始工作。

你们会建议我开始编写单元测试吗?

当然好。首先阅读Beck的“测试驱动开发”

我认为这可能对我的实际实施有所帮助,但是我再次觉得这可能会使我慢下来。

现在它使您减速。但是,当您不得不调试数小时而不是数天时,您会改变主意。

任何建议将不胜感激:)

测试。相信我。不幸的是,它也必须是一项管理策略,但这可以节省时间。无论现在还是将来,测试都将保留在那里。当您更改平台并运行测试,并且这些测试仍然有效时,您知道您的工作可以按预期进行。


8

编辑:我当然加入了TDD pro / con小狗,并跳过了问题1:

1-在ASP.net网络表单中实施单元测试:

首先,如果您认为可以将它们与MVC结合使用,则可以像疯子一样为之奋斗。作为前端/ UI开发人员,.net MVC是帮助我停止讨厌.net的原因,因此我可以更好地专注于讨厌我遇到的每个Java Web解决方案。单元测试是有问题的,因为Web表单确实模糊了服务器和客户端之间的界限。在进行单元测试的任何尝试中,我都将重点放在数据处理上,并且(希望如此)假设webforms在后台为您处理用户输入的规范化。

2-首先考虑一下单元测试是否值得:

好吧,全披露:

  • 我大多是自学成才。我的正式培训可以归结为一个JavaScript,一个PHP和一个C#类,以及我自己对OOP原理的个人研究,以及从诸如Design Patterns之类的内容中读取的内容。

然而,

  • 我主要是为客户端Web编写的,其中的实际编程部分是关于动态类型,一流函数和对象可变性的最快速,最宽松的语言之一。

这意味着,我不会为同一编译器或虚拟机编写程序。我为三种语言编写了4-20种不同的解释(是的,其中两种只是声明性的,而且有时会以不同的方式确定我正在使用的UI的基本物理空间),并且由于这样做是一种比今天多样化得多。不,我不只是一个为一次性应用程序插入JQuery内容的孩子。我帮助构建和维护相当复杂的东西,并且具有很多UI元素复杂性。

因此,是的,如果您的设计技能完全被废话,或者您将大量中等水平的开发人员丢给1-2个质量开发人员问题,那么在这里和那里进行一些调整的机会很多,从而造成大量的失败。

我对TDD应该为您提供的服务的理解是,测试实际上更多地是在迫使您更仔细地考虑设计并使您专注于需求。足够公平,但是这里的问题是,它颠覆了您应该为接口设计的工作,而又巧妙地改变了根本上与为接口测试而设计的根本不同的东西。对我来说,区别在于,您可以画出一张清晰的图片(妈妈不必去猜测它的含义),然后以非常快的速度用绿色填充整个页面,这样您就可以成为第一个将蜡笔拍在桌子上然后大喊“做完了! ” 通过将优先级转移到结果而不是流程和设计上,您基本上是在鼓励继续实施垃圾代码,这通常是问题的根本所在。

然后当然还有单元测试本身的“非现实意义”,但经常被称赞为它的副作用,可帮助您检测回归误差。TDD倡导者往往对这是否是目标还是仅仅是一个令人讨厌的副作用,IMO有点一厢情愿,因为他们知道该死的还是至少怀疑这根本不足以确定您的缺陷代码,尤其是在像JavaScript这样的动态语言中,假设您甚至可以预测一长串依赖关系中的每种可能情况都是很困难的。

在JS中有一个进行自动测试的地方,但是比将单元测试附加到与另一个代码所接触的每个“单元”上要好得多,这可以确保您没有大量的时间首先,重复工作或其预期用途在语义上不明确的垃圾对象。您遵循DRY原则。当这样做的价值显而易见时(而不是在一分钟之前),您可以将其抽象出来以供重用/可移植。您遵循的是胡萝卜而不是坚持原则,建立了一致的流程和做事方式(即,以正确的方式使用您的东西很容易打扰想要以错误的方式做事)。并且由于对foo和bar的热爱,您永远不会沉迷于级联级联的大量继承模式的反模式中,以作为代码重用的一种方式。

以上所有这些都帮助我以严重的方式减少了代码中难以诊断的错误,对于作为开发人员使用浏览器集的人来说,这比把“呃,在未指定的文件中,在此假想行号处的对象类型为“对象”的问题。” (谢天谢地,感谢IE6)在我的工作中,TDD不会鼓励这些事情。在过程中,只要点A和B之间的关系并不重要,它将把焦点转移到100%的结果上。浪费时间可以更好地用于确保您的内容清晰,可移植且易于修改,而不会在第一时间造成很多混乱。

或者,也许我只是对我所扎根的范式过于草率,但我认为,首先做到这一点,比掩盖自己或其他人时的屁股更有效地利用时间。团队做错了。而且没有什么可以强迫您考虑设计而不是仅仅实施一些东西。设计应该成为每个程序员的骨干。对于任何“强迫您”做正确的事情或保护您免受自身伤害的事情,都应该以对IMO蛇油瓶的保留同样的怀疑来对待。如果您还不知道,现代IT和一般开发中的蛇油是以液体吨出售的。


1
我写了很多单元测试。没有他们,我不会写代码。但是我同意你的观点。测试应指导而不是推动开发。您无法自动思考问题。
FizzyTea

我没有自动测试的问题。但是对我来说,在各个时刻大规模采用它似乎更像是恐慌而不是过程。在您可能会与各种您无法控制的事物进行交互的点上节省设计时间以及自动化测试和验证的时间是我喜欢的。
艾里克·雷彭

5

以我的经验,当您开始使用单元测试并坚持使用它们时,即测试驱动开发,单元测试确实非常有用。原因如下:

  • 单元测试迫使您在编写所需代码之前,先考虑一下您想要什么,以及如何验证您是否想要它。在TDD方案中,您首先编写测试。因此,您必须知道将要编写的代码需要做什么,以及如何验证它是否成功完成,才能编写“红色”测试,然后通过将代码编写为“绿色”来进行测试。交上来。在许多情况下,这迫使您多考虑一些要编写的算法以通过此测试,这总是一件好事,因为它可以减少逻辑错误和“拐角情况”。在编写测试时,您在考虑“这怎么失败”和“我在这里没有测试什么”,这导致了更强大的算法。

  • 单元测试迫使您考虑如何使用将要编写的代码。在我学到TDD之​​前,有很多次我写代码期望一种依赖方式起作用,然后又得到了一位同事编写的依赖方式,该方式以完全不同的方式工作。尽管TDD仍然可以做到这一点,但是您正在编写的单元测试迫使您考虑如何使用正在编写的对象,因为这是该对象的示例用法。然后,您将希望以易于使用的方式编写对象,并在必要时进行调整(尽管对预定义的接口进行编程是解决此问题(不需要TDD)的更好的整体解决方案)。

  • 单元测试允许您“通过测试进行编码”。如果允许的话,像ReSharper这样的重构工具可能是您最好的朋友。在定义测试的用法时,可以使用它来定义新功能的框架。

    例如,假设您需要创建一个新对象MyClass。您首先创建MyClass实例。“但是MyClass不存在!” ReSharper抱怨。按下Alt + Enter,您会说“然后创建它”。并且,您已经有了您的类定义。在测试代​​码的下一行中,您将调用方法MyMethod。“但是不存在!” ReSharper说。重复此操作,然后重复另一个Alt + Enter。您只需按几次键就可以定义新代码的“骨架”。当您继续充实用法时,IDE会在不适合的地方告诉您,通常解决方案非常简单,以至于IDE或插入其中的工具都知道如何修复它。

    更极端的例子符合“ Triple-A”模型;“安排,行动,断言”。设置所有内容,执行要测试的实际逻辑,然后断言该逻辑正确。用这种方式编码,将解决方案编码到测试中是很自然的。然后,只需按几下按键,就可以提取该逻辑并将其放置在生产代码中可以使用的位置,然后对测试进行一些小的更改,使其指向逻辑的新位置。这样做会迫使您以模块化,易于重用的方式构建代码,因为您仍必须可访问要测试的单元。

  • 单元测试的运行速度比您可以想到的任何手动测试快许多数量级。在大型项目中,很快就会遇到一个问题,即单元测试的时间开销开始通过减少手动测试所花费的时间来弥补。您必须始终运行刚刚编写的代码。传统上,您是通过启动大型程序,在UI中导航以设置要更改其行为的情况来手动执行的,然后通过UI或以生成的数据的形式再次验证结果。如果代码是TDD的,则只需运行单元测试。我保证,如果您正在编写好的单元测试,则后一种选择的速度将比前者快许多倍。

  • 单元测试赶上了回归。同样,在学习TDD之前,有很多次,我认为是对一段代码进行了外科手术更改,验证了该更改在报告的情况下纠正了错误的行为(或产生了新的期望行为)。 ,然后将其检入发行版,结果发现更改完全打破了在完全相同的模块中重用同一代码块的其他情况。在TDDed项目中,我可以编写一个测试来验证我将要进行的更改,进行更改,然后运行完整套件,如果代码的所有其他用法都是TDD的并且我的更改损坏了某些内容,则针对这些进行测试其他事情会失败,我可以调查。

    如果开发人员必须手动查找并测试可能受潜在更改影响的所有代码行,那么将一事无成,因为确定更改影响的成本会使更改变得不可行。至少,没有任何东西是固体的,因此很容易维护,因为您永远都不敢触摸可能在多个地方使用的东西。相反,您会针对这种情况推出自己的非常相似但又包含您的小变更解决方案,这违反了SRP甚至可能是OCP,并慢慢将您的代码库变成了一个拼凑而成的被子。

  • 单元测试通常以一种有利的方式对形状体系结构进行测试。单元测试是与任何其他逻辑隔离的测试。为了能够单元测试你的代码,那么,你必须把代码写在这样一种方式,你可以隔离它。因此,良好的设计决策(例如松耦合和依赖注入)自然会从TDD流程中摆脱出来;必须注入依赖项,以便在测试中可以注入“模拟”或“存根”,以针对所测试的情况生成输入或处理输出,而不会产生“副作用”。TDD的“使用第一”的心态通常导致“接口编码”,即松散耦合的本质。这些良好的设计原则可让您在生产中进行更改,例如替换整个类,而无需更改大量代码库以适应。

  • 单元测试表明代码有效,而不是证明代码有效。乍一看,您可能会认为这是不利的;证据肯定比示范好吗?理论很棒;计算和算法理论是我们工作的基础。但是,一个的正确性的数学证明算法不是对的正确性的证明实施。数学证明仅表明遵守该算法的代码应该正确。单元测试表明实际编写的代码可以实现您认为的功能,因此可以证明它正确的。通常,这比理论上的证明更有价值。

综上所述,单元测试有一些缺点:

  • 您不能对所有内容进行单元测试。您可以设计系统以最大程度地减少单元测试未涵盖的LOC数量,但是很简单,系统中的某些区域无法进行单元测试。您的数据访问层在被其他代码使用时可以被嘲笑,但是数据访问层本身包含许多副作用,并且通常无法对很多(大多数?)存储库或DAO进行单元测试。同样,使用文件,建立网络连接等的代码具有内置的副作用,您根本无法对执行此操作的代码行进行单元测试。UI元素通常无法进行单元测试;您可以测试诸如事件处理程序之类的代码隐藏方法,可以对构造函数进行单元测试并验证是否插入了处理程序,但是根本没有代码替代用户在特定图形元素上单击鼠标并观看被调用的处理程序。在可以进行和不能进行单元测试的范围之间达到这些界限称为“刮擦沙箱的边缘”。超出这一点,您将只能使用集成测试,自动验收测试和手动测试来验证行为。

  • 没有TDD不能应用单元测试的许多优点。编写代码,然后编写行使代码的测试是完全可能的。它们仍然是“单元测试”,但是通过首先编写代码,您将失去“测试优先”开发中固有的许多优势:代码不一定以易于测试的方式进行架构,甚至无法在生产中使用; 您不会获得编写测试和思考未曾想到的内容时固有的“仔细检查”思维过程;您不通过测试进行编码;如果编写代码,手动测试它并看到它能工作,然后编写失败的单元测试代码,这是错误的代码还是测试?您的主要优势是防止回归(当以前通过其测试的代码现在失败时,您会收到警报)以及高速验证与手动测试。TDD的丢失”

  • 单元测试带来了开销。很简单,您正在编写代码以测试所编写的代码。这必然会增加开发项目的总LOC,是的,测试的LOC可能会超过实际项目的LOC。天真的开发人员和非开发人员都会查看这种情况,并说测试是浪费时间。

  • 单元测试需要纪律。您必须编写能够充分利用代码库的测试(良好的代码覆盖率),必须定期运行它们(就像每次提交更改时都应运行完整套件一样),并且必须使所有内容保持“绿色”(所有测试均通过)。当事情中断时,您必须通过修复不符合期望的代码或通过更新测试的期望来解决它们。如果您更改测试,则应该问“为什么”,并密切注意自己;简单地更改失败的断言以匹配当前行为,或者简单地删除失败的测试,这是令人难以置信的诱惑。但是,这些测试应该基于需求,而当两者不匹配时,您会遇到问题。如果这些事情没有完成,

  • 单元测试需要更多设备。满足上述纪律要求以及人类自然变得懒惰和自负的自然趋势的通常解决方案是运行“连续集成”软件包(例如TeamCity,CruiseControl等)的“ build-bot”,该软件包执行单元测试,计算代码覆盖率指标,并具有其他控件,例如“ triple-C”(符合编码约定,la FxCop)。构建机器人的硬件必须具有合理的性能(否则它将无法跟上普通团队将要执行的代码检入率),并且机器人的检入程序必须保持最新(如果创建了新的单元测试库,必须更改运行单元测试的构建脚本才能在该库中查找。这项工作比听起来少,但是通常至少需要团队中的一些人员具备一定的技术知识,他们知道各种构建过程的精髓是如何工作的(因此可以在脚本中自动执行它们并维护所述脚本)。它还仍然需要纪律,即在构建“中断”时要注意,并在检入新内容之前修复(正确)导致构建中断的所有内容。

  • 单元测试可能迫使代码以非理想的方式进行架构。尽管TDD通常有利于代码的模块化和可重用性,但它可能不利于代码的正确可访问性。如果单元测试直接使用了放置在生产库中的对象和成员,则它们不能是私有的或内部的。当其他编码人员现在看到一个对象时,如果他们应该改用库中存在的其他内容,则尝试使用该对象可能会导致问题。代码审查可以帮助解决此问题,但这可能是一个问题。

  • 单元测试通常禁止“快速应用程序开发”编码样式。如果您正在编写单元测试,则不是在编写“快速松散”代码。通常这是一件好事,但是当您处于无法控制的最后期限内,或者您正在实施范围非常小的变更,而利益相关者(或您的老板)想知道为什么所有这些麻烦都必须发生时,只需更改一行代码,就不可能编写和维护适当的单元测试套件。敏捷过程通常通过允许开发人员在时间要求方面有发言权来提供帮助。请记住,业务员要做的就是说“是的,我们可以”,他们会得到佣金支票,除非流程涉及必须实际完成工作的人说“不能,我们不能”并且要注意。但是,不是每个人的敏捷,敏捷都有其自身的局限性。


4

正确使用单元测试会很有用。您的逻辑存在各种问题。

凯文说:“开发时,我会经历很多次反复试验。”

巧合地看一下编程。最好了解代码应该做什么,然后通过单元测试证明它可以做到。不要仅仅因为运行程序时UI不会中断就认为代码起作用!

s writing unit tests for ASP.NET web forms something that is done often

我不了解统计数据,无法说明人们测试网络表单的频率。不要紧。用户界面很难测试,单元测试也不应与用户界面耦合。将您的逻辑分成可测试的层,类库。与后端逻辑分开测试UI。

So, question number 2: Would you guys advise me to start writing unit tests?

是。一旦您习惯了它们,他们就会加快您的速度。与最初的开发阶段相比,维护要花费更多的时间和金钱。单元测试,甚至是初始测试和错误修复,都大大有助于维护。


有一些测试框架可以在aspx页面上进行测试,以简单地测试页面在不同情况下是否成功加载。这可能不够好,但总比没有好。
2012年

4

在实践上,由于一个非常重要的原因,单元测试可能非常有用: 您可以一次测试一点代码。

当您编写一整套复杂步骤并在最后调试它们时,您往往会发现许多错误,并且该过程通常会更加困难,因为您会在过程中走得更远。在Visual Studio中,您可以生成一个快速测试,对其进行一些更改,然后仅运行该测试 ..然后,您知道,例如调用该方法的方法可以依靠它。因此,它增加了信心。

单元测试不能证明您的程序是正确的!:单元测试检查敏感代码中的回归,并允许您测试编写的新方法。

单元测试并不是要测试前端:将单元测试视为您创建的用于测试正在编写的新方法或类似内容的小项目,而不是将代码满足的某种条件。

单元测试非常适合协作环境:如果我必须向使用完全不同的技术来挖掘的同事提供一种方法,例如他从iOS调用它,那么我可以快速编写单元测试以查看是否他想要的方法a)检索正确的数据b)根据他的规范执行c)没有任何讨厌的瓶颈。


“单元测试无意测试前端”谁这么说?我没有质疑。我只想知道,因为很多人似乎有一个错误的主意,我想用那些不是很有影响力的TDD倡导者资源说的话来赞叹他们。
Erik Reppen

有些人(包括我自己)在第一次遇到单元测试时就有这种误解,所以我只是想澄清一下。我不知道您为什么要挑战我,实际上我完全同意您的看法。
Tjaart

4

似乎没有涉及的一件事是测试不同类型的代码的复杂性。

当您使用简单的输入和输出来处理内容时,通常易于进行单元测试,并且如果选择测试时要考虑所测试代码的边缘情况,它们将使您对它们的正确性充满信心。不为此类代码编写测试会很疯狂。(请注意,进入库的大多数代码都满足此条件。)

但是,在其他情况下,由于代码正在处理复杂的基础数据(通常是数据库,但不一定非要使用)或生成非常复杂的输出数据(想像一个例程,将种子值添加到游戏级别中(这是一个非常极端的示例),而单元测试则几乎没有用。涵盖测试用例中的所有内容通常是梦pipe以求的,当您发现错误时,几乎总是一个您从未想到过的用例,因此无论如何也无法构建测试。

没有银弹,任何描绘成银弹的东西都没有支持者所声称的那么好,但这并不意味着它没有用。


4

为ASP.NET Web窗体应用程序编写单元测试并不常见,而且很难完成。但是,这并不意味着项目应该跳过它。

单元测试是corner stones关键任务应用程序,它们为核心功能按预期提供可靠性和安心。

实际上,您可以使用可用于您的Web表单开发中的ASP.NET MVP模式,更轻松地引入单元测试。它将引入关注点和关注点分离ability to write essential unit tests

一些参考可能对您有所帮助:


2
+1不管是否进行单元测试都没关系,使用MVP是处理Web Forms(也许也是WinForms)的一种方式。
simoraman 2012年

3

单元测试的目的是使您可以安全地更改代码(重构,改进等),而不必担心会破坏系统内部的某些内容。当您不真正知道代码更改的所有影响(尽管耦合松散)时,这对于大型应用程序非常有用。


6
没有恐惧是一种错误的安全感。
编码器

也许“更少的恐惧”是一个更好的短语,但是答案在很大程度上还是正确的。
布伦丹·朗

1
@Coder如果您的测试在重构之后通过,那么您可以确定代码方面的改进不会改变应用程序的行为方式(大致而言,您没有引入任何新的错误,但这不一定是正确的,因为您无法实现100%的测试覆盖率)。
m3th0dman 2012年

2

我的经验,单元测试很有用。

单元测试是关于隔离所编写代码的各个部分,并确保它们独立工作。这种隔离实际上可能涉及创建伪造或模拟对象以与您正在测试的对象进行通信,也可能不会。这在很大程度上取决于对象的体系结构。

单元测试可带来多种好处:

首先,这可以确保您测试的单元以开发人员认为的工作方式工作。虽然这听起来不尽人意:但这不一定表示该对象运行正常;只是它在按您认为的方式工作,这比没有单元测试所必需的反复试验方法要强得多。

此外,它可以确保单元继续按照您(开发人员)认为应该工作的方式工作。软件更改,这可能会以意想不到的方式影响设备。

另一个副作用是,它意味着你测试单位孤立的工作。通常,这意味着您开始对接口而不是具体的类进行编程,从而减少耦合并增加内聚力:这是良好设计的所有常见标志。是的:仅使您的代码单元具有单元测试就自然会改善代码的设计。

然而...

单元测试不是测试的结束。仍然需要其他类型的测试。例如,即使您已经证明了单元的工作方式与期望的一样,您仍然需要证明每个单元的工作方式与与之交谈的单元都希望其工作。也就是说,您的组件是否正确相互集成?


1

对于要测试的层数较少的简单应用程序(主窗口->子菜单),也许可以运行以检查更改。

对于较大的应用程序,要花30秒钟的时间才能导航到要测试的页面,这最终会非常昂贵。


1

我想为此添加新的一面(实际上是一个较旧的一面):如果您的代码设计合理,则单元测试不是那么有用。

我知道大多数程序员现在不再做程序设计,我仍然做,我发现单元测试是适应TDD文化的相当耗时的必要条件,但是到目前为止,我只遇到了1-2个次要我的代码每千行测试中的错误-不仅是我写的,而且是官方测试人员写的。

我想您也不会再进行程序设计了-当前的人们会迫使您不要做程序设计-但也许值得记住的是,与设计相比,单元测试是一种非常低效的方法。

这是一个dijsktraian参数:单元测试只能针对足够详细且可以运行的程序执行。

如果在实际编写代码之前为代码绘制流程图/状态/动作图,序列图,对象清单(以及最后和最不重要的类图),则只需四处寻找,便可以消除大多数潜在的威胁性错误。您的台词并检查您的姓名。

如今,类图是由代码生成的,包含数百个,有时甚至数千个类,并且完全不可用-包含15个以上元素的任何内容都是人类无法识别的。

您必须知道什么是10 + -5类或现在要做什么,并且必须从多个角度检查您的代码,每个图完全代表您正在查看的角度,并且您将杀死数千个错误在纸上。

  • 检查动态代码是否符合类型(只需在流程图上简单显示输入/输出类型,然后用铅笔或其他颜色将它们连接起来),
  • 检查是否处理了所有状态(通过检查条件的完整性),
  • 跟踪线以确保一切都结束了(对于具有数学思维的人,每个流程图都应该是完全定义的确定性有限状态自动机
  • 确保某些组件彼此无关(通过提取所有组件的依赖关系)(对安全性有好处)
  • 通过将依赖关系转换为关联关系来简化代码(依赖关系来自成员方法使用的类)
  • 检查名称,查找通用前缀,删除同义词等...

有这么简单...

此外,我发现我的应用程序更有用,如果它们直接来自用例...如果用例编写得很好(ACTOR动词主题,维护者要求打开自动提款机)...

我编写的代码覆盖了95%以上的代码。当然,我有时会做单元测试。在计算中进行边界检查,但是我还没有遇到严重的回归(严重:24小时内不会消失),甚至没有在重构中使用单元测试。

有时候我只是连续绘图三到四天都没有做一行代码。然后,在一天之内,我输入1500-2000行。到第二天,它们基本上已经准备就绪。有时编写单元测试(覆盖率超过80%),有时(另外)要求测试人员尝试打破它,每一次,有些人被要求通过查看它进行审查。

我还没有看到那些单元测试能找到任何东西。

我希望设计思想可以代替TDD ...但是TDD如此简单,就像用大锤一样...设计需要思考,而且大多数时候您都不会使用键盘。


我认为您可以在键盘上进行设计。但是,计划和组织代码确实需要花费更多的精力,而不仅仅是简单地在类可能是单片函数的类中通过输入到输出进行绊脚石。
埃里克·雷彭

相信我,单片函数不适合A4(或US Letter)纸张。有时候,当我懒惰的时候,我会在键盘上进行“设计”,但是那是不同的质量。每当我进行认真的开发时,我都会使用UML进行设计。在绘制这些代码时,您试图以一种非常受限制但仍然结构化的方式向自己和其他人解释发生了什么,并且当您能够从各个角度解释代码时,只有输入它,突然之间您所有的错误最多都是键入错误...
Aadaam
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.