我刚刚获得了CS学位,并且目前正在担任初级.NET开发人员(C#,ASP.NET和Web表单)的工作。回到我上大学时,单元测试的主题确实涵盖了,但是我从来没有真正看到它的好处。我了解应该执行的操作,即确定代码块是否适合使用。但是,我以前从未真正编写过单元测试,也从未感到有此需要。
正如我已经提到的,我通常使用ASP.NET Web表单进行开发,并且最近,我一直在考虑编写一些单元测试。但是我对此有一些疑问。
我读到单元测试通常是通过编写“模拟”进行的。虽然我理解了这个概念,但似乎无法弄清楚应该如何为完全动态的网站编写模拟文件,并且几乎所有内容都依赖于来自数据库的数据。例如:我使用很多具有ItemDataBound事件等的转发器(同样取决于“未知”数据)。
问题1:是否为ASP.NET Web编写单元测试经常做一些事情,如果是的话:如何解决“动态环境”问题?
开发过程中,我会经历反复的尝试。那并不意味着我不知道我在做什么,而是我通常写一些代码,按Ctrl F5看看会发生什么。尽管这种方法大多数时候都能奏效,但有时我会觉得自己有些笨拙(因为我的经验不足)。有时我也会像这样浪费很多时间。
所以,问题2:你们会建议我开始编写单元测试吗?我认为这可能对我的实际实施有所帮助,但是我再次觉得这可能会使我放慢速度。
#1 楼
我认为:是的,应该的。它们使您对所做的更改充满信心(其他所有操作仍然有效)。这种信心是塑造代码所需要的,
否则您可能会害怕更改。
它们使您的代码变得更好;大多数简单的错误都在单元测试中被发现。尽早地发现并修复缺陷总是比后来的修复更便宜,例如,当应用程序投入生产时。
它们充当其他开发人员的文档,说明您的代码如何工作以及如何使用。
您遇到的第一个问题是ASP.NET本身并不能帮助您编写单元测试。 -实际上,它对您不利。如果有任何选择,请开始使用ASP.NET MVC,它是在考虑单元测试的情况下创建的。如果不能使用ASP.NET MVC,则应在ASP.NET中使用MVP模式,以便至少可以轻松地对逻辑进行单元测试。
除此之外,您只需要精通编写单元测试即可。如果您练习TDD,那么您的代码将被创建为可测试的-换句话说,很漂亮。
我建议您练习并配对程序。阅读时:
敏捷软件开发,原理,模式和实践[精装]
头等优先的设计模式
GOF设计模式圣经
/> Roy osherove对单元测试的第一个介绍
单元测试模式圣经
或者,对于第一个概述:
SOLID
Xunit测试图案
评论
刚开始进行单元测试,我必须同意。与TDD方法相结合,它不仅是一种好习惯,而且非常有用,而且还可以节省大量时间。我不认为即使添加新功能或修复错误后,我也无法运行单元测试并验证一切是否正常,我的项目可能没有用。我无法想到以其他任何方式进行回归测试。
– kwelch
2012年7月24日19:51
如果单元测试用作文档,则有问题。阅读500行代码以了解5行代码是如何工作的。
–编码器
2012年7月24日21:00
@Coder:当您测试更高级别的方法时,它确实涉及超过5行代码。
–user2567
2012年7月24日在21:11
@coder:一个类的文档告诉您该类实例提供的服务。它告诉您有关在较大上下文中如何使用此类实例的信息,即对象之间的交互的信息较少。在某些情况下,测试为您提供了典型的交互代码,这是非常宝贵的起点,因此很多时候我都无法数清。
– Stefano Borini
2012年7月24日在21:14
@Coder:这不是在记录代码的作用,而是在记录该代码固有的假设,即为什么以及何时应该起作用。如果通过更改SUT使基本假设无效,则一个或多个单元测试将失败。这并不能替代更高级别的设计/体系结构文档,甚至XML文档,但它涵盖了那些东西永远无法做到的事情。
– Aaronaught
2012年7月25日在0:50
#2 楼
否。单元测试背后的概念基于一个前提,即自从发明单元测试之前就已经知道它是假的:测试可以证明您的代码正确的想法。 >
拥有大量全部通过的测试只能证明一件事和一件事:您拥有大量全部通过的测试。它不能证明测试的内容符合规范。它不能证明您的代码没有编写测试时从未考虑过的错误。 (而且您认为要测试的事情是您正在关注的可能问题,因此无论如何您都可能正确解决了这些问题!)最后但并非最不重要的一点是,它并不能证明测试本身就是代码,没有错误。 (遵循最后一个逻辑结论,您最终会陷入乌龟。)
Djikstra早在1988年就废弃了“将测试作为正确性证明”的概念,以及他今天写的遗嘱仍然有效:
指出程序测试可能已经令人信服地证明了错误的存在,但距现在已经二十年了,但是永远不可能
/>证明他们的缺席。在认真引用了这个广为人知的言论后,软件工程师回到了日常工作中,
继续完善他的测试策略,就像
yore的炼金术士一样,他继续完善他的温柔净化。
单元测试的另一个问题是,它会在您的代码和测试套件之间建立紧密的耦合。当您更改代码时,您希望会出现一些会破坏某些测试的错误。但是,如果由于需求本身已发生更改而要更改代码,则会导致很多失败的测试,并且必须手动检查每个测试并确定测试是否仍然有效。 (而且,尽管这种情况不太常见,但仍然有可能通过了现有的测试,因为您忘记更改了需要更改的内容。该测试应该是无效的。)
单元测试只是最新的长期的开发风潮有望使编写工作代码变得更容易,而实际上并没有成为一个好的程序员。他们中没有一个人兑现了自己的诺言,也没有做到。真正了解如何编写工作代码根本没有捷径。
有一些报道说,在稳定性和可靠性至关重要的情况下,自动测试确实有用。例如,SQLite数据库项目。但是,对于大多数项目而言,要达到它们的可靠性水平是非常不经济的:测试与实际SQLite代码的比率接近1200:1。大多数项目都负担不起,反正也不需要它。
评论
证明您的代码对于那些测试而言行为正确。如果你问我,那真是令人惊讶。
– Stefano Borini
2012年7月24日19:35
今天谁真正相信单元测试是“正确性证明”?没有人应该这么认为。您是正确的,他们只证明那些测试通过了,但这比编写单元测试之前多了一个数据点。您需要在许多不同的层进行测试,以使您自己了解代码质量。单元测试不能证明您的代码没有缺陷,但是它们确实提高了您的信心(或应该……),使您相信代码可以执行您设计的工作,并且明天继续执行今天的工作。
–布莱恩·奥克利(Bryan Oakley)
2012年7月24日20:09
>但是,如果测试本身存在错误,则您将具有错误的信心;如果手动测试仪未正确执行其工作,则您还将具有错误的信心。
– Stefano Borini
2012年7月24日20:31
@MasonWheeler:对不起,Mason,我不相信。在超过二十年的编程中,我认为我个人从未见过这样的情况:测试给人一种错误的安全感。也许其中一些确实做到了,我们已经修复了它们,但是这些单元测试的成本绝没有超过拥有它们的巨大收益。如果您不编写单元级代码就能够编写代码,那么我印象深刻,但是那里的绝大多数程序员都无法始终如一地做到这一点。
–布莱恩·奥克利(Bryan Oakley)
2012年7月24日在20:32
如果您编写了通过代码的错误测试,则代码也可能也存在错误。编写错误代码和错误测试的几率远低于单独的错误代码。单元测试不是万能的。它们是一个很好的额外层(谨慎使用),可以减轻手动测试人员的负担。
– Telastyn
2012年7月24日在20:43
#3 楼
如果您曾经看到编写一种主要方法来测试学校的一小段代码的好处,那么单元测试就是该实践的专业版/企业版。还要想象一下构建代码,启动本地Web服务器,浏览到相关页面,输入数据或将输入设置为适当的测试种子,提交表单以及分析代码的开销。结果... vs构建并点击了nUnit运行按钮。
这也是一个有趣的图像...
我在这里找到了该图像:http ://www.howtogeek.com/102420/geeks-versus-non-geeks-when-doing-repetitive-tasks-funny-chart/
评论
是的你可以。在单元测试中隔离Web层以测试Web配置(URLS,输入验证等)。通过存根Web和数据库层,您可以在没有数据库或Web服务器的情况下测试业务层。我使用dbunit测试我的数据库。如果愿意,您仍然可以进行完整的集成测试,但是我将其作为开发后的特定任务来进行。
–希思·里利(Heath Lilley)
2012年7月24日在21:44
可爱的图形,但比例尺严重错误。正如我在回答中指出的那样,SQLite的完整测试覆盖率要求测试中的代码比产品本身中的实际代码多大约1200倍。即使您对全覆盖并不那么着迷,您仍然需要比产品多几倍的测试才能达到测试中任何有用的覆盖水平。为了在此处准确无误,该红线的垂直部分必须不断向上移动并持续大约3页的长度。
–梅森·惠勒
2012年7月24日在22:55
@MasonWheeler这是一匹非常好的马,我想它可能已经死了……SQLite有很多测试,因为它的数据库真该死。我想确保可以。再举一个例子,Silverstripe框架的tests:code比率接近1:1,因此SQLite并不具有代表性。
–匹配
2012年7月25日,0:24
@MasonWheeler您在芝诺(Zeno)的第一个悖论中失败了。如果要将此图像放大到SQLite的测试级别,则X轴也必须扩展到其当前长度的100倍左右。
–伊兹卡塔
2012年7月25日在1:53
当您是后端开发人员而又没有时间开始制作黑白网页,以便您可以测试后端逻辑时,尤其如此。我所要做的只是提供模拟请求和会话对象,并通过单元测试运行我的控制器,最后我知道它是对还是错。如果使用DI,则只需注入一个与内存数据库交互的DAO,以使您的数据库不会发生变化,或者简单地引发Exception并捕获它,从而不会提交数据。我对单元测试说是。
– Varun Achar
2012年7月25日在6:43
#4 楼
如今,单元测试对此有些神秘。人们将其视为100%的测试覆盖率是一个圣杯,并且好像单元测试是开发软件的唯一途径。他们没有抓住重点。
单元测试不是答案。现在是测试。
现在,每当讨论开始时,都会有人(甚至是我)嘲笑Dijkstra的名言:“程序测试可以演示错误的存在,但从不演示错误的存在。” Dijkstra是正确的:测试不足以证明软件能够按预期工作。但是这是必要的:在某种程度上,必须有可能证明软件正在执行您想要的功能。
许多人都在手工测试。即使是坚定的TDD爱好者也可以进行手动测试,尽管有时他们会拒绝这样做。它无济于事:就在您进入会议室将软件演示给客户/老板/投资人/等之前,您将手工操作以确保其能正常工作。这没什么不对,事实上,即使没有100%的单元测试覆盖率和对测试最大的信心,只是期望一切都能顺利进行而无需手动运行(也就是测试),这将是疯狂的。
但是,即使构建软件是必需的,手动测试也很少。为什么?因为手动测试是繁琐且耗时的,并且是由人工执行的。而且,众所周知,人类在执行乏味且耗时的任务方面很糟糕:他们避免在可能的情况下执行任务,并且在被迫执行任务时往往做得不好。
机械手,擅长执行乏味且耗时的任务。毕竟,这就是发明计算机的目的。
因此测试至关重要,而自动化测试是确保测试始终如一的唯一明智的方法。随着软件的开发,测试和重新测试很重要。这里的另一个答案指出了回归测试的重要性。由于软件系统的复杂性,对系统某一部分的频繁看似无害的更改会导致系统其他部分的意外更改(即错误)。没有某种形式的测试,您将无法发现这些意外更改。而且,如果您想获得有关测试的可靠数据,则必须以系统的方式执行测试,这意味着您必须拥有某种自动化的测试系统。
所有这些与做什么有什么关系?单元测试?好吧,由于其性质,单元测试是由机器而不是人工运行的。因此,许多人误以为自动化测试等于单元测试。但这不是真的:单元测试只是超小型自动化测试。
现在,超小型自动化测试的价值是什么?优点是它们可以隔离测试软件系统的组件,从而可以更精确地确定测试目标,并有助于调试。但是单元测试本质上并不意味着更高质量的测试。由于它涵盖了更精细的软件水平,因此通常会导致更高质量的测试。但是可以完全测试整个系统的行为,而不能测试其组成部分,然后仍然进行彻底的测试。
但是即使具有100%的单元测试覆盖率,系统也可能无法经过全面测试。因为单个组件可以孤立地完美工作,但是一起使用时仍然会失败。因此,单元测试虽然非常有用,但不足以确保软件按预期运行。实际上,许多开发人员通过自动集成测试,自动功能测试和手动测试来补充单元测试。
如果您没有在单元测试中看到价值,那么也许最好的入门方法是使用另一种自动化测试。在Web环境中,使用诸如Selenium之类的浏览器自动化测试工具通常会以较小的投资获得巨大的成功。将脚趾浸入水中后,您将更容易看到自动化测试的帮助。而且,一旦有了自动化测试,单元测试就变得更加有意义,因为它可以提供比大型集成或端到端测试更快的周转时间,因为您可以将测试仅针对当前正在使用的组件进行测试。 br />
TL; DR:不必担心单元测试。只需担心先测试软件。
评论
单元测试也是由人类编写的,可能是错误的。
–大濑O
2012年8月5日上午11:00
#5 楼
这取决于这是一个答案,在软件开发中,您会看到很多答案,但是单元测试的实用性实际上取决于它们的编写程度。标称数量的单元测试非常有用,它可以检查应用程序的功能以进行回归测试。但是,检查应用程序返回的大量简单测试可能毫无用处,并且会产生错误的安全感,因为应用程序“具有大量的单元测试”。
此外,您作为开发人员的时间宝贵,花时间编写单元测试不花时间编写新功能。同样,这并不是说您不应该编写单元测试,但是每个公共函数都需要单元测试吗?我认为答案是否定的。执行用户输入验证的代码是否需要单元测试?由于它是应用程序中的常见故障点,因此很有可能。
这是经验发挥作用的领域之一,随着时间的推移,您会认识到可以从单元测试中受益的应用程序部分,但是要达到这一点还需要一段时间。
评论
这是个好的观点。您必须知道如何编写良好的测试来捕获SUT的价值。
–安迪
2012年7月25日在12:54
这基本上涵盖了如何教我进行单元测试的方法-首先编写测试以涵盖最重要/易碎的情况,然后在您的假设被证明错误时添加更多测试(我认为某些事情“永远不会失败”,但确实如此) 。
–布伦丹·朗(Brendan Long)
2012年7月25日15:57
#6 楼
为大学班级完成的项目与您将在工作中编写的业务应用程序有很大不同。不同之处在于寿命。大学计划“居住”多长时间?在大多数情况下,它在您编写第一行代码时开始,并在获得标记时结束。您可以说,实际上,它仅在实施时有效。 “发布”通常等于其“死亡”。为企业开发的软件超出了大学项目的发布死亡点,并且只要业务需要就可以继续使用。很长在谈论金钱时,没有人会花一分钱就拥有“更酷更整洁的代码”。如果25年前用C编写的软件仍然可以运行并且足够好(据其业务所有者的需要可以理解),则应要求维护它(添加新功能,改进旧功能-更改源代码)。
我们得出了非常重要的一点-回归。在我工作的地方,我们有两个团队维护两个应用程序。一个是大约5到6年前写的,几乎没有代码测试覆盖率*,第二个是第一个具有完善测试套件(单元,集成以及其他功能)的应用程序的较新版本。两个团队都有专门的手动(人类)测试人员。是否想为一线团队引入一个相当基本的新功能需要多长时间? 3至4周。这段时间的一半是“检查其他一切是否仍然有效”。对于手动测试人员来说,这是繁忙的时间。电话响了,人们不高兴,有些东西又坏了。第二小组通常会在不到3天的时间内处理此类问题。
我绝不说单元测试会使您的代码容易出错,更正或其他您能想到的花哨的词。他们都没有使虫子神奇地消失。但是,当与自动软件测试的其他方法结合使用时,它们使您的应用程序比以前具有更高的可维护性。这是一个巨大的胜利。
最后但并非最不重要的一点是,Brian的评论,我认为这是整个问题的主要内容:
(...)他们确实提高了您对代码确实的信心(或应该...)您设计了它要执行的操作,并且明天继续执行今天的操作。
因为在今天和明天之间,有人可能会做出微小的更改,这将导致非常重要的报告生成器代码崩溃。测试使您在客户做不到这一点之前就发现了这一点。
*他们确实在其代码库中慢慢引入了越来越多的测试,但是我们都知道东西通常看起来。
评论
+1000用于链接到Software_Regression。大学将单元测试暴露为您必须出于纯粹的信念而要做的事情,而没有详细解释存在一种疾病可以预防和控制,并且该疾病称为回归。 (然后存在回归测试与单元测试非常不同的问题,因为我发现唯一能很好地解释它的读物是本书的免费样本)
– ZJR
2012年7月24日在23:47
#7 楼
正在编写针对ASP.NET Webforms的单元测试,这是经常要做的事情,如果是的话:我该如何解决“动态环境”问题?
常做。使用UI元素并不是单元测试所擅长的,因为没有很好的方法来以编程的方式验证正确的事物最终出现在屏幕上的正确位置。
伙计们劝我开始编写单元测试?我认为这可能会对我的实际实施有所帮助,但是再次让我觉得这可能会使我放慢速度。
在适用的情况下,是的。对于以可重复的方式验证特定案例,它们将非常有帮助。它们有助于抵御麻烦的更改,并在进行更好的更改时起到故障保护的作用。
要注意的一件事是,它们通常会使您的速度变慢。
没关系。
现在花一点时间(如果做得好)将可以节省将来的时间,因为它们可以捕获一些错误,防止某些错误的再次发生,并使您的工作量有所增加。可以在代码中进行其他改进以防止其腐烂。
评论
+1用于实际回答问题的用例!其他所有人都跳到“单元测试”部分,而忘记了“ ASP.NET Web表单”部分。
–伊兹卡塔
2012年7月25日在1:55
这是一个很好的答案,值得更多批评,这是因为您已经解决了问题,并且诚实地评估了放慢开发人员的速度,并且不保证会捕获或阻止每个错误,这是完全正确的。完全正确。
– Mantorok
2012年7月25日15:55
#8 楼
我刚刚获得了CS学位,并且目前正在从事.NET初级开发人员的工作(使用C#,通常是ASP.NET,Web表单-不是
ASP.NET MVC) 。回到我上大学时,单元测试的主题确实涵盖了,但是我从来没有真正看到它的好处。
那是因为您从来没有编写过大程序。
我知道应该怎么做-确定是否要编写代码块>适合使用-但是我以前从未真正编写过单元测试。 (我也从未感到过需要..)
编写单元测试会强制您的代码符合可测试,有据可查且可靠的给定思维结构。它远远超出了您的要求。
-我读过单元测试通常是通过编写“模拟”来进行的。虽然我了解这个概念,但似乎无法弄清楚应该如何为完全动态的网站编写模拟文件,而几乎所有内容都取决于来自数据库的数据。
使用模拟类模拟对数据库的访问,该模拟类返回填充有定义明确的硬编码数据的模型类。或者,用夹具数据填充测试数据库并从那里开始工作。
-你们建议我开始编写单元测试吗?
当然好。首先阅读Beck的“测试驱动开发”。
我认为这可能对我的实际实现有所帮助,但是我再次觉得它可能会使我慢下来。
现在使您减速。但是当您不得不调试数小时而不是数天时,您会改变主意。
任何建议将不胜感激:)
测试。相信我。不幸的是,它也必须是一项管理策略,但这可以节省时间。无论现在还是将来,测试都将保持不变。当您更改平台并运行测试,并且这些测试仍然有效时,您就会知道自己的工作可以按预期进行。
#9 楼
编辑:我当然参加了TDD pro / con的审查,并跳过了问题#1:1-在ASP.net Webforms中实施单元测试:
首先,如果您认为可以让他们去做,与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和一般发展中的蛇油是以液体吨出售的。
评论
我写了很多单元测试。没有他们,我不会写代码。但是我同意你的观点。测试应该指导而不是驱动开发。您不能自动考虑问题。
– FizzyTea
2012年7月26日19:46
我没有自动测试的问题。但是对我来说,在各个时刻大规模采用它似乎更像是恐慌而不是过程。在您可能会与各种您无法控制的事物进行交互的点上节省时间进行设计以及自动进行测试和验证是我喜欢的方式。
–埃里克·雷彭(Erik Reppen)
2012年7月26日20:16
#10 楼
以我的经验,当您开始使用单元测试并坚持使用它们时,即测试驱动开发,单元测试确实非常有用。原因如下:单元测试迫使您在编写编写所需代码之前,先考虑一下自己想要的东西,以及如何验证是否得到了它。在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的,而我的更改却破了某些东西,则对这些进行测试其他事情将失败,我可以进行调查。
如果开发人员必须手动查找和测试可能受潜在更改影响的所有代码行,那么将一事无成,因为确定更改影响的成本会使更改变得不可行。至少,没有什么是SOLID,因此很容易维护,因为您永远都不敢触摸可能在多个地方使用的东西。相反,您会针对这种情况推出自己的非常相似但结合了您的小变更解决方案,违反了SRP甚至可能是OCP,并慢慢将您的代码库变成了拼凑的被子。
单元以典型的优势测试形状架构。单元测试是与任何其他逻辑隔离的测试。为了能够对您的代码进行单元测试,必须以隔离代码的方式编写代码。因此,良好的设计决策(如松散耦合和依赖注入)自然会从TDD流程中摆脱出来;必须注入依赖项,以便在测试中可以注入“模拟”或“存根”,从而为被测试的情况生成输入或处理输出,而不会产生“副作用”。 TDD的“使用第一”的心态通常会导致“接口编码”,即松散耦合的本质。这些良好的设计原则可让您在生产中进行更改,例如替换整个类,而无需更改大量代码库以适应。
单元测试表明该代码有效,而不是证明该代码有效。乍一看,您可能会认为这是不利的;证据肯定比示范好吗?理论很棒;计算和算法理论是我们工作的基础。但是,算法正确性的数学证明不是实现正确性的证明。数学证明仅表明遵守该算法的代码应该是正确的。单元测试表明,实际编写的代码可以完成您认为会做的事情,因此可以证明它是正确的。通常,这比理论证明更有价值。
现在,所有这些都说明单元测试有缺点:
您可以对所有内容进行单元测试。您可以设计系统,以最大程度减少单元测试未涵盖的LOC数量,但是很简单,系统中的某些区域无法进行单元测试。您的数据访问层在被其他代码使用时可以被嘲笑,但是数据访问层本身包含很多副作用,并且通常无法对很多(大部分?)存储库或DAO进行单元测试。同样,使用文件,建立网络连接等的代码具有内置的副作用,您根本无法对执行此操作的代码行进行单元测试。 UI元素通常无法进行单元测试;您可以测试诸如事件处理程序之类的代码隐藏方法,还可以对构造函数进行单元测试并验证是否插入了处理程序,但是根本无法用代码替代用户在特定图形元素上单击鼠标并观看被调用的处理程序。在可以进行和不能进行单元测试的范围之间达到这些界限称为“刮擦沙箱的边缘”。超出这一点,您将只能使用集成测试,自动验收测试和手动测试来验证行为。
没有TDD,单元测试的许多优点就不适用。编写代码,然后编写行使代码的测试是完全可能的。它们仍然是“单元测试”,但是通过首先编写代码,您会失去“测试优先”开发中固有的许多优势:代码不一定以易于测试的方式进行架构,甚至无法在生产中使用;您不会获得编写测试和思考您从未考虑过的内容时固有的“仔细检查”的思维过程;您不通过测试进行编码;如果编写代码,手动测试它并看到它能工作,然后编写失败的单元测试代码,这是错误的代码还是测试?您的主要优势是预防回归(当以前通过其测试的代码现在失败时,您会收到警报)以及高速验证与手动测试。失去TDD的其他优势可能会完全失去使用单元测试的平衡。
单元测试会带来开销。很简单,您正在编写代码以测试所编写的代码。这必然会增加开发项目的总LOC,是的,测试的LOC可能会超过实际项目的LOC。天真的开发人员和非开发人员都会查看这种情况,并说测试是浪费时间。
单元测试需要纪律。您必须编写能够充分利用代码库的测试(良好的代码覆盖率),必须定期运行它们(例如,每当提交更改时,应运行完整套件),并且必须使所有内容保持“绿色”(所有测试均通过)。当事情中断时,您必须通过修复不符合期望的代码或通过更新测试的期望来解决它们。如果您更改测试,则应该问“为什么”,并密切注意自己;简单地更改失败的断言以匹配当前行为,或者简单地删除失败的测试,这是令人难以置信的诱惑。但是,这些测试应该基于需求,而当两者不匹配时,您会遇到问题。如果未完成这些操作,则当您使用每一个代码来显示您上次触摸测试时实际正在编写的代码时,测试就没有价值(这是为了证明您最初的开发或手术更改已实现您的想法)当你写的时候。)
单元测试需要更多设备。满足上述纪律要求以及人类自然变得懒惰和自负的自然趋势的通常解决方案是运行“连续集成”软件包(例如TeamCity,CruiseControl等)的“ build-bot”,该软件包执行单元测试,计算代码覆盖率指标,并具有其他控件,例如“ triple-C”(符合编码约定,la FxCop)。构建机器人的硬件必须具有合理的性能(否则它将无法跟上一般团队将执行的代码检入率),并且机器人的检入程序必须保持最新(如果创建了一个新的单元测试库,必须更改运行单元测试的构建脚本才能在该库中查找。这项工作比听起来要少,但是通常至少需要团队中一些人的一些技术知识,他们知道各种构建过程的精髓是如何工作的(因此可以在脚本中自动执行它们并维护所述脚本) 。它还仍然需要纪律,即在构建“中断”时要注意,并修复任何导致构建中断的内容(正确),然后再进行任何新的检查。
单元测试可以强制在非结构化代码中进行构建。理想的方式。尽管TDD通常有利于代码的模块化和可重用性,但它可能不利于代码的正确可访问性。如果单元测试直接使用了放置在生产库中的对象和成员,则它们不能是私有的或内部的。当其他编码人员现在看到一个对象时,如果他们应该改用库中存在的其他内容,则尝试使用该对象可能会导致问题。代码审查可以帮助解决此问题,但这可能是一个问题。
单元测试通常禁止“快速应用程序开发”编码样式。如果您正在编写单元测试,则不是在编写“快速松散”代码。通常这是一件好事,但是当您处于无法控制的最后期限内,或者您正在实施范围非常小的变更,而利益相关者(或您的老板)想知道为什么所有这些麻烦都必须发生时,更改一行代码,编写和维护适当的单元测试套件就完全不可行。敏捷流程通常通过允许开发人员在时间要求方面有发言权来提供帮助。请记住,业务员要做的就是说“是的,我们可以”,他们会得到佣金支票,除非过程中涉及必须实际完成工作的人说“不能,我们不能”并且要注意。但是,不是每个人的敏捷,敏捷都有其自身的局限性。
#11 楼
正确使用单元测试非常有用。逻辑上有各种各样的问题。“当我在开发时,我会经历很多次反复试验。”凯文说。
巧合地看一下编程。最好先了解代码应该做什么,然后通过单元测试证明它可以做到。不要仅仅因为运行程序时UI不会中断就认为代码起作用!
s writing unit tests for ASP.NET web forms something that is done often
我不了解统计数据,无法说明人们多久测试一次Web表单。不要紧。用户界面很难测试,单元测试也不应与用户界面耦合。将您的逻辑分成可测试的层,类库。与后端逻辑分开测试UI。
So, question number 2: Would you guys advise me to start writing unit tests?
是。一旦您习惯了它们,他们就会加快您的速度。维护比最初的开发阶段要花费更多的时间和成本。维护可以通过单元测试得到很大帮助,甚至可以进行初始测试和错误修复。
评论
有一些测试框架可以在aspx页面上进行测试,以简单地测试页面在不同情况下是否成功加载。这可能不够好,但总比没有好。
–敬畏
2012年7月25日12:44
#12 楼
在实践上,由于一个非常重要的原因,单元测试可能非常有用:一次可以测试一个代码。
编写完整的复杂步骤序列并在最后调试它们时,您往往会发现许多错误,而且由于要逐步进行,因此该过程通常会更困难。在Visual Studio中,您可以生成一个快速测试,对其进行一些更改,然后仅运行该测试。.然后,您知道,例如调用该方法的方法可以依靠它。因此,它增加了信心。
单元测试不能证明您的程序是正确的!:单元测试检查敏感代码中的回归,并允许您测试编写的新方法。
单元测试并非旨在测试前端:将单元测试视为您创建的用于测试正在编写的新方法或类似内容的小项目,而不是将代码作为某种条件需要满足。
单元测试非常适合协作环境:如果我必须向使用完全不同的技术进行挖掘的同事提供一种方法,例如他从iOS调用它,那么我可以很快编写单元测试以查看他是否要使用该方法
a)检索正确的数据
b)根据他的规范执行
c)没有任何令人讨厌的瓶颈。
评论
“单元测试无意测试前端”谁这么说?我没有质疑。我只想知道,因为很多人似乎有错误的主意,所以我想用那些不是很有影响力的TDD倡导者来源说的话来赞叹他们。
–埃里克·雷彭(Erik Reppen)
2012年7月26日在20:09
有些人(包括我自己)在第一次遇到单元测试时就有这种误解,所以我只是想澄清一下。我不知道您为什么要挑战我,实际上我完全同意您的看法。
– Tjaart
2012年7月27日在5:58
#13 楼
似乎没有涉及的一件事是测试不同类型的代码的复杂性。当您使用简单的输入和输出处理事物时,通常易于进行单元测试以及测试是否容易在选择要测试的代码的极端情况时会带给您很多自信,因为它们是正确的。不为此类代码编写测试会很疯狂。 (请注意,进入库的大多数代码都符合此条件。)
但是,在其他情况下,由于代码要处理复杂的基础数据(通常是数据库),因此最终不得不构造大型模拟程序进行测试但这不是必须的),也不是产生非常复杂的输出数据(例如,将例程的种子值转换为游戏级别的例程),并且单元测试并不是那么有用。覆盖测试用例中的所有内容通常是梦pipe以求的事情,当您发现错误时,几乎总是一个您从未想到过的情况,因此无论如何也无法构建测试。
银色子弹,任何描绘成银色子弹的东西都没有支持者声称的那么好,但这并不意味着它没用。
#14 楼
为ASP.NET Web窗体应用程序编写单元测试并不常见,而且很难完成。但是,这并不意味着项目应该跳过它。单元测试是关键任务应用程序中的
corner stones
,它们提供了可靠性和安心性,核心功能按预期运行。 实际上,您可以使用可用于Web表单开发的ASP.NET MVP模式,更轻松地引入单元测试。它将引入关注点和
ability to write essential unit tests
。 一些参考可能对您有所帮助:
开始测试ASP.NET Webforms应用程序(MVP模式)
执行和使用ASP.NET Web窗体MVP测试重定向
使用ASP.NET的Model View Presenter-CodeProject
评论
+1不管是否进行单元测试都没关系,使用MVP是处理Web Forms(也许也是WinForms)的一种方式。
– Simoraman
2012年7月25日4:35
#15 楼
单元测试的目的是使您可以安全地更改代码(重构,改进等),而不必担心会破坏系统内部的某些内容。当您不真正知道代码更改的所有影响(尽管耦合松散)时,这对于大型应用程序非常有用。评论
没有恐惧是一种错误的安全感。
–编码器
2012年7月24日在20:47
也许“更少的恐惧”是一个更好的短语,但是答案在很大程度上还是正确的。
–布伦丹·朗(Brendan Long)
2012年7月25日在16:07
@Coder如果您的测试在重构之后通过,那么您可以确定代码方面的改进不会改变应用程序的行为方式(大致而言,您没有引入任何新的错误,但这不一定是正确的,因为您无法实现100%的测试覆盖率)。
–随机42
2012年7月25日在16:37
#16 楼
根据我的经验,是的,单元测试很有用。单元测试是关于隔离所编写代码的各个部分,并确保它们独立工作。这种隔离实际上可能涉及创建假对象或模拟对象以与您正在测试的对象进行通信,也可能不会。这在很大程度上取决于对象的体系结构。
单元测试可以带来各种好处:
从根本上讲,这可以确保测试的单元以您,开发人员,认为他们应该工作。虽然这听起来不尽人意:但这不一定表示该对象运行正常;只是它按照您认为的方式工作,这比没有单元测试所必需的反复试验方法要强得多。
此外,它还可以确保单元继续工作。开发人员认为您应该工作的方式。软件更改,这可能会以意想不到的方式影响单元。
另一个副作用是,这意味着您测试的单元可以独立工作。通常,这意味着您开始针对接口而不是具体的类进行编程,从而减少耦合并提高内聚力:这是良好设计的所有常见标志。是的:仅使您的代码单元具有单元测试就自然会改善代码的设计。
......
单元测试并不是测试的终点。仍然需要其他类型的测试。例如,即使您已经证明了单元的工作方式与期望的一样,您仍然需要证明每个单元的工作方式与与之交谈的单元期望其工作方式一样。也就是说,您的组件是否正确集成在一起?
#17 楼
对于要测试的层数较少的简单应用程序(主窗口->子菜单),也许可以运行以检查更改。对于较大的应用程序,需要30秒钟的导航时间才能获取该页面您正在测试,这最终会非常昂贵。.
#18 楼
我想为此添加新的一面(实际上是相当旧的一面):如果您的代码设计合理,则单元测试不是那么有用。我知道大多数程序员都不会我现在仍然做程序设计,并且发现单元测试是适应TDD文化的相当耗时的必要条件,但是到目前为止,我每千行测试中只遇到了1-2个小错误。代码-不仅是我写的,而且是官方测试人员写的。
我想您也不会再进行程序设计了-当前的人们会迫使您不要这样做-但也许值得记住的是,单元测试的效率非常低与设计相比的方法。
这是一个dijsktraian参数:单元测试只能针对足够详细且可以运行的程序执行。
如果绘制流程图/状态/在实际编写代码之前,请先编写代码的动作图,序列图,对象清单(以及最后和最不重要的类图),您只需跟踪行并检查名称,就能消除大多数潜在的潜在错误。
如今,类图是由代码生成的,包含数百个,有时甚至数千个类,并且完全不可用-包含15个以上元素的任何内容都是人类无法识别的。
您必须知道10 + -5类很重要或现在要做什么,并且能够从多个角度检查代码,每个图完全代表您正在查看的角度,并且您将杀死纸上的数千个错误。
检入动态代码是否满足类型(只需在流程图上显示输入/输出类型并用铅笔将它们连接起来即可)或其他颜色),
检查是否处理了所有状态(通过检查条件的完整性),
跟踪线以确保一切都结束了(每个流程图都应该是完全定义的确定性有限状态自动机,这是出于数学考虑
确保某些组件彼此无关(通过提取其所有依赖项) (对安全性有好处)
通过将依赖关系转换为关联来简化代码(依赖关系来自成员方法使用的类)
检查名称,查找通用前缀,清除同义词
等...
就这么简单...
此外,我发现我的应用程序更有用,如果它们直接来自用例...如果用例编写得很好(ACTOR动词主题,维护者要求打开自动提款机)...
我编写的代码覆盖了95%以上的代码覆盖率,当然,有时我会进行单元测试,尤其是用于计算中的边界检查,但由于未使用单位t,我尚未遇到严重的回归(严重:24小时内不会消失)甚至为了重构也很麻烦。
有时候,我不会连续3-4天只写一行代码,只是画图。然后,在一天内输入1500-2000行。到第二天,它们大部分已经准备就绪。有时编写单元测试(覆盖率超过80%),有时(另外)要求测试人员尝试打破它,每一次,有些人被要求通过查看它来进行审查。
我还没有看到那些单元测试能找到任何东西。
我希望设计思想能够代替TDD ...但是TDD如此简单,它就像用大锤一样...设计需要思考,而且大部分时间您都不在键盘上。
评论
我认为您可以在键盘上进行设计。但是,计划和组织代码确实需要花费更多的精力,而不是简单地在可能也是单片函数的类中通过输入到输出进行绊脚石。
–埃里克·雷彭(Erik Reppen)
2012年7月26日19:49
相信我,单片函数不适合A4(或US Letter)纸张。有时候,当我懒惰的时候,我会在键盘上进行“设计”,但这并不相同。每当我进行认真的开发时,我都会使用UML进行设计。当您绘制这些代码时,您试图以一种非常受限制但仍然结构化的方式向自己和其他人解释发生了什么,并且当您能够从各个角度解释代码时,只有输入突然间,您所有的错误最多都是打字错误...
– Aadaam
2012年7月26日在20:12
评论
我只想说这另一面是有些地方要求他们作为练习。我现在在一个地方,除非单元测试涵盖了我们,否则我们不允许检入代码。因此,需要注意的一点是,即使您可能不相信它们,也可能会要求您一路走来。我认为这是一种有用的技能,可以在您的武器库中学习和使用。我常常会从测试中发现我的代码中的小错误和不幸,几乎是与自己进行一些QA会话。没有编写任何单元测试,您如何准确地涵盖单元测试?这是“给自己定级”还是“设计自己的课程”学校之一?
单元测试是有问题的-非常适合动态语言,使用的语言越严格,实用性就越差,但是请务必进行构建测试。它们不必是单元测试,但是您确实应该具有可以使用JUnit运行的集成测试,这非常有用。
-1对不起,即使我不同意我的观点,我也全力以赴,但这是错误的。没有人声称“单元测试用于捕获新代码中的错误”,所以这是一个稻草人-单元测试用于捕获回归。而且Dijkstra的报价是在上下文之外进行的-上下文是,如果您对问题有正式的规范,则测试是没有意义的。
“单元测试用于捕获回归”。否。自动化测试用于捕获回归。回归总是要求相同的测试运行数百次,因此值得将它们自动化。不幸的是,关于这个问题的许多回答和评论实际上都在解决“自动化测试是否有帮助?”这个问题。单元测试可能是自动化测试的一种形式,但它们的重点完全不同。我当然认为自动化测试值得称赞,但是不应用作证明单元测试(或TDD)的理由。