我在一家小公司工作,担任单人开发人员。实际上,我是该公司唯一的开发人员。我有几个(相对)定期编写和维护的大型项目,但是没有一个项目可以支持它们。在开始新项目时,我经常想知道是否应该尝试TDD方法。这听起来像是个好主意,但老实说,我永远无法证明所涉及的额外工作。我意识到,肯定有一天另一位开发人员将不得不维护我的代码,或者至少要对其进行故障排除。我将事情保持尽可能简单,并评论和记录难以掌握的事情。而且事实是,这些项目没有那么大或太复杂,以至于一个体面的开发人员很难理解它们。代码的各个方面。由于我是唯一的开发人员,而且我非常接近整个项目中的代码,因此遵循写后手动测试模式的效率要高得多。我还发现需求和功能的更改足够频繁,以至于维护测试会给项目增加相当大的阻力。本来可以花时间解决业务需求的时间。投资回报率太低。

我偶尔会进行一些测试,以确保我正确编写了算法,例如根据聘用日期计算某人在公司的年限。但是从代码覆盖率的角度来看,我已经涵盖了大约1%的代码。

在我的情况下,您是否仍然可以找到一种使单元测试成为常规做法的方法,或者我有理由避免那开销?

更新:关于我的情况,我遗漏了一些东西:我的项目都是Web应用程序。要覆盖我的所有代码,我必须使用自动化的UI测试,在该领域中,与手动测试相比,我仍然看不到很大的好处。

评论

谢谢大家。我在这里学到很多东西。关于我的情况,我遗漏了几件事:我的项目都是Web应用程序。要覆盖我的所有代码,我必须使用自动化的UI测试,在该领域中,与手动测试相比,我仍然看不到很大的好处。

使用Telerik的Web自动化测试工具,我们在Transactis上取得了巨大的成功。我们已经将许多以前的手动浏览器测试转换为自动化。自动化测试的速度更快,并且对于突出显示您的网站可能存在的任何性能问题也非常有用。
我见过一个试图对整个网页进行自动浏览器测试的项目。据我所知,它没有发现我们通过手动测试发现的数百个严重错误中的任何一个,并且花费了大量的时间进行开发和维护。 (使用由NUnit驱动的Selenium)。更糟糕的是,由于浏览器和测试框架的不兼容性,某些测试经常会因非问题而中断。

这不是一个真正的答案,只是一个观察……您反对单元测试的论点是因为“需求变化太频繁”使我想起了我听到的相反论点:“我们的程序是如此静态,测试的重点是什么它几乎永远都不会改变!” ;)

Web应用程序的自动化UI测试不是单元测试,它们是完全不同的野兽,如果您不想执行这些操作,我不会怪您。但是您所有的业务代码都应该在后端,这就是您应该测试的内容。

#1 楼


我见过的许多测试示例都涉及细节,涵盖了代码的所有方面。


那么?您不必测试所有内容。只是相关的事情。


由于我是唯一的开发人员,而且我非常接近整个项目中的代码,因此遵循写-后-手动测试模式。


实际上是错误的。效率不高。这真的只是一个习惯。

其他单独开发人员所做的工作是编写草图或轮廓,编写测试用例,然后用最终代码填充轮廓。 />

我还发现需求和功能的变化足够频繁,以至于维护测试会在项目上增加相当大的阻力。测试不是阻力。需求变更就是阻力。

您必须修复测试以反映需求。无论是细节还是高级;首先编写或最后编写。

测试通过之前,代码没有完成。那就是软件的普遍真理。

您可以进行有限的“这就是”验收测试。

或者可以进行一些单元测试。

也可以同时使用。

但是无论您做什么,总会有一个测试来证明该软件可以工作。 d建议一点形式性和漂亮的单元测试工具套件使该测试更加有用。

评论


我喜欢您的第一句话,只是测试相关内容。关于手动测试与单元测试的效率,我不相信我的陈述是完全错误的,也不是你的说法完全正确。似乎要在自动和手动测试之间找到平衡,以实现最高效率。

– Ken Pespisa
2011年4月8日在16:38

@肯·佩斯皮萨:对不起。我大约两年前喝了TDD Kool-Aid(经过30年的最后测试)。现在我坚持测试优先。它使我的工作效率提高了很多,因为在构建时我的想法很少。

– S.Lott
2011年4月8日在16:42

@Ken Pespisa:不。答案是均衡的。如果问是否用零除是正确的,则会因某种原因得到压倒性的反应。如果您问sqrt(-1)是否应为整数,则会得到压倒性的响应,且倾向于一种方式。平衡点围绕“如何”和“什么顺序”。绝对的事实是您必须进行测试。因此,请首先编写测试并确保它们可以正常工作。

– S.Lott
2011年4月8日在17:02

考虑单元测试接口而不是实现细节。测试限制和边界情况。测试有风险的代码。尽管检查代码比检查别人的代码更容易出错,但许多代码很容易通过检查进行验证。第一次进行手动测试可能会更有效率,到第十次自动化测试就可以进行了。

–BillThor
2011年4月8日在17:21

“直到测试通过,代码才完成”-IMO,不是。代码通过测试后即开始。直到使用了一年或两年,并经过庞大,活跃和急躁的用户群进行的压力测试和集成测试后,代码才可以使用。那是唯一真正重要的测试。

–矢量
2013年6月16日5:15



#2 楼

想象一下,您有一整套的测试可以眨眼就能亮起绿灯或红灯。想象一下,这组测试可以测试所有内容!想象一下,运行测试套件所需要做的就是键入^ T。这会给您带来什么力量?

您可以更改代码而不必担心破坏某些东西吗?
您可以添加新功能而不必担心破坏一个旧功能吗? >您可以快速清理凌乱的代码而不必担心造成损害吗?

是的,您可以做所有这些事情!随着时间的流逝,您的代码会发生什么?它会变得越来越清洁,因为没有清洁的风险。每次您编写一行代码时,妖精都会在测试套件中添加一些内容,以测试该行代码是否达到了预期的目的。因此,每隔两秒钟您可以点击^ T,并看到编写的最后一行代码有效。

您认为应该进行多少调试?

如果听起来像是幻想,那是对的。但是现实并没有太大的不同。只需几秒钟即可替换眨眼,而将TDD学科替换为仙女,您几乎已经做到了。

比方说,您要回到一年前构建的系统,却忘记了如何创建中心对象之一。有一些测试可以以各种方式创建该对象。您可以阅读这些测试并慢跑您的记忆。需要调用API吗?有一些测试以各种方式调用该API。这些测试都是小文件,用您理解的语言编写。它们是完全明确的。他们是如此正式以至于执行。而且他们不能与应用程序不同步!

不值得投资吗?你在开玩笑吧!谁会不想要那套测试?帮自己一个忙,不要再为愚蠢而吵架了。学习做好TDD,并观察执行速度有多快,代码有多干净。

评论


哇,鲍伯叔叔?在这里表达您的想法真是太好了。我同意您对TDD的好处,确实没有论据。问题是时间和投资回报率。考虑这些事情对我来说并不愚蠢。想象一下,一个项目将使我完成TDD所花的时间比没有它多50%,而仙女告诉我,在项目的整个生命周期中,与手动测试相比,它仅节省10%的时间。这似乎是一个幻想,但我认为在某些项目中这完全是合理的。

– Ken Pespisa
2011年4月9日,下午1:34

@Ken“想像一下,完成一个TDD会比没有一个项目多花费我50%的时间”。在我看来,这听起来像是幻想。实际上,听起来您只是在现场提出了这个建议,而没有丝毫证据来支持它。

– Rein Henrichs
2011年4月29日下午5:11

@Rein Henrichs-当然我把这个数字加起来,这是一个假设性的陈述。我要指出的是,TDD会为项目增加大量时间,因此我必须考虑是否要获得相等或更好的价值。我坚信,您不必说服TDD的价值。但这不是万能药。

– Ken Pespisa
2011年4月29日在16:09

@Rein,“可用证据”到底是什么?请详细说明。

– Ken Pespisa
2011年5月23日在22:15

@叔叔鲍勃“用几秒钟的时间来眨眼”:当然,你在开玩笑。 TDD是一个很好的工具,但是您只需要测试相关的部分,否则您将花费​​更多的时间维护测试,而不是进行任何认真的开发。当需求变化非常快时,尤其如此:您一直在为不断变化的类编写和丢弃测试。我并不是说TDD不好,它必须明智地使用,而不能像您建议的那样机械地应用。

–乔治
13-10-10在20:37

#3 楼

您所犯的错误是,您将测试视为没有立即回报的时间投资。

首先编写测试实际上会将您的注意力集中在代码的这一部分需要做的事情上。

其次运行它们会发现可能会导致错误的错误在测试中出现。

第三次运行它们有时会显示一些本来不会在测试中出现的错误,然后真的会在生产中咬你。

第四次如果您在运行的系统中遇到了一个错误并为其创建了单元测试,则以后将无法重新引入该错误。那可能是一个很大的帮助。重新引入的错误很常见而且很烦人。

第五,如果您需要将代码移交给其他人,则测试套件将使他们的工作变得更加轻松。另外,如果您忽略了一个项目并在几年后重返项目,那么您将再也无法接近它了,它将对您也有帮助。

我的经验一直以来一直以来,在项目的整个开发过程中,进行适当的单元测试始终使过程更快,更可靠。

评论


@Ken,测试套件是可执行形式的规范。

–user1249
2011年4月26日在20:29

#4 楼

JUnit(Java单元测试框架)的人有一种哲学,即如果测试太简单,就不要对其进行测试。我强烈建议您阅读他们的“最佳实践常见问题解答”,因为它非常实用。

TDD是编写软件的不同过程。单元测试的基本前提是,您将减少调试器中单步执行代码的时间,并更快地确定您的代码更改是否意外破坏了系统中的其他内容。这与TDD相符。 TDD周期如下所示:


编写测试
观察它是否失败(证明您有事要做)
写出进行测试所需的内容通过-再也没有了。应用TDD是因为它会改变您编写代码的方式。通过强迫自己考虑如何测试/验证代码是否正常,您正在编写可测试的代码。而且由于我们正在谈论单元测试,所以通常意味着您的代码变得更加模块化。对我来说,模块化和可测试的代码是一个大赢家。

现在,您是否需要测试C#属性之类的东西?想象一下这样定义的属性:

bool IsWorthTesting {get; set;}


答案将是“否”,这不值得测试,因为此时您正在测试语言功能。只需相信C#平台的家伙做对了。此外,如果失败了,您应该怎么做才能解决它?这意味着不要这样做,但请确保测试棘手的问题所使用/使用的代码: Java有很多这样的东西。即使没有黑客破解已安装的文件也无法失败,您仍需要编写catch块或声明已检查的异常。
用户界面。找到被测控件,并调用正确的事件来模拟用户的操作非常麻烦,在某些情况下是不可能的。但是,如果您使用模型/视图/控制器模式,则可以确保模型和控制器已经过测试,而视图部分则由手动测试完成。
客户端/服务器交互。这不再是单元测试,现在是集成测试。写出通过网络发送和接收消息所需的所有部分,但实际上不要经过网络。一个好的方法是减少实际通过有线与原始通信进行通信的代码的责任。在您的单元测试代码中,模拟通信对象以确保服务按预期运行。

信不信由你,TDD将帮助您进入可持续的发展步伐。这不是因为魔术,而是因为您有一个紧密的反馈循环,并且能够迅速发现真正愚蠢的错误。解决这些错误的成本基本上是恒定的(至少足以用于计划目的),因为小错误永远不会变成大错误。将其与代码狂暴/调试清除冲刺的突发性进行比较。

#5 楼

您必须在测试成本与bug成本之间取得平衡。

为打开文件的函数编写10行单元测试是毫无意义的。 />
对复杂的数据结构执行复杂操作的函数-显然是的。

介于两者之间的是一个棘手的问题。但是请记住,单元测试的真正价值不是测试特定功能,而是测试它们之间的棘手交互。因此,进行一次单元测试可以发现,只需更改一点代码,就可以破坏相距1000行的其他模块中的某些功能,因此值得一试。

#6 楼

测试就是赌博。

创建一个测试是一个赌注,即在一个单元中发生错误而不是通过该测试捕获错误的成本(现在以及以后的所有代码修订中)要比开发测试的成本高。这些测试开发成本包括诸如增加测试工程的工资单,增加的上市时间,因不编写其他代码而损失的机会成本等。

像任何赌注一样,有时您会赢,有时您会赢。失去。

有时错误少得多的较晚软件会赢得快速但有错误的东西,这些东西首先进入市场。有时相反。您必须查看自己特定领域的统计数据,以及管理层想要赌博的程度。 ,从统计上讲,这不值得花时间创建其他特定测试。但是有时,错误的成本如此之高(医疗,核能等),以至于公司必须下输(类似于购买保险)。许多应用程序没有那么高的故障成本,因此不需要更高的不经济保险。其他人也可以。

评论


好答案。真正能够回答我最初问题的少数几个。自从写这篇文章以来,我一直沉浸在测试领域(我喜欢它。)在我真正地知道何时使用(或不使用)它之前,我需要更多地了解它。由于此处所述的许多原因,我希望一直使用它。但这最终取决于我的速度,因为最终这是我的时间的赌注,这是在公司/客户的控制之下,而且通常他们专注于项目三角形的底角: wikipedia.org/wiki/Project_triangle

– Ken Pespisa
2011年4月27日在1:09

#7 楼

我的建议是仅测试您想要正常工作的代码。

不要测试您想要进行错误检查的代码,并给您带来麻烦。

评论


让我想起了我的牙医所说的话:您不必用牙线清洁所有牙齿,只需要用牙线清洁即可。

– Ken Pespisa
2011年4月9日23:55

我承认这就是让我想到的。 ;-)

–尼克·霍奇斯(Nick Hodges)
2011-4-10 23:45

#8 楼


我经常想知道我是否应该尝试TDD方法。这听起来像是个好主意,但老实说,我永远无法证明所涉及的额外工作。


TDD和单元测试不是一回事。

您可以编写代码,然后再添加单元测试。那不是TDD,而是很多额外的工作。

TDD是在“红灯”循环中进行编码的实践。绿灯。重构迭代。

这意味着编写尚不存在的代码的测试,观察测试失败,修复代码以使测试工作,然后使代码“正确”。这通常可以节省您的工作

TDD的优点之一是它减少了对琐事的思考。一站式错误之类的东西消失了。您不必遍历API文档,只需查找返回的列表是从0还是1开始即可。

评论


您能详细说明一次失误如何消失吗?您是说要比通过文档搜索更快地得到关于数组索引是从零开始还是从一开始的答案?对我来说似乎不太可能-我在Google上的速度非常快:)

– Ken Pespisa
2011年4月8日在16:41

实际上,编写TDD是探索API(包括用于记录功能的传统代码库)的绝佳方法。

–坦率的剪毛
2011年4月8日在17:00

如果该API发生更改,它也非常有用...您突然测试失败:-)

–bitsoflogic
2011年4月8日19:05

@Ken Pespisa,绝对更快-根据您认为是0还是1编写代码,运行它,并在需要时进行修复。在大多数情况下,您会是对的,而您不必跳过查找,如果您输入错了,您会在10秒钟之内知道。

– Paul Butcher
2011年4月9日15:55

非常有趣的好处。我有点喜欢

– Ken Pespisa
2011年4月9日23:53

#9 楼

我在一个可以测试几乎所有东西的系统上工作。测试中值得注意的执行是PDF和XLS输出代码。

为什么?我们能够测试收集数据的零件并建立用于创建输出的模型。我们还可以测试确定模型的哪些部分将进入PDF文件的部分。我们无法测试PDF看起来是否正常,因为那完全是主观的。我们无法测试PDF的所有部分是否对典型用户可读,因为这也是主观的。或者,如果条形图和饼图之间的选择对于数据集是正确的。

如果输出将是主观的,则几乎没有单元测试,您可以做值得的工作。

评论


实际上,这种测试可能是“集成测试”。是的,集成测试要比单元测试难得多,原因之一是有时“正确”的规则非常复杂,甚至是主观的。

–sleske
2014年9月6日22:01在

#10 楼

在很多情况下,“编写然后手动测试”只需要花费一些时间即可编写几次测试。节省时间来自能够随时重新运行这些测试。

考虑一下:如果您的测试具有不错的功能覆盖范围(不要与代码覆盖范围混淆),并且假设您具有10个功能-单击一个按钮意味着大约有10个重新测试...当您坐下来喝咖啡时。

您也不必测试Minutae。如果您不想深入了解细节,可以编写涵盖功能的集成测试... IMO,某些单元测试的语言和平台(而不是代码)的测试粒度太细。 >
; TL; DR实在不适合,因为这样做的好处实在太好了。

#11 楼

专业的开发人员编写单元测试是因为从长远来看,它们可以节省时间。您将迟早测试您的代码,如果您不这样做,则用户会这样做,并且如果以后必须修复错误,它们将变得更难修复,并且产生更多的影响。 />如果您编写的代码没有测试且没有错误,那就很好。我不相信您可以用零错误来编写一个非平凡的系统,所以我假设您正在以一种或另一种方式对其进行测试。

在修改或重构较旧的代码时,单元测试对于防止回归也至关重要。他们不会证明您的更改没有破坏旧代码,但可以给您带来很大的信心(只要他们当然可以通过:))对已经交付的代码进行测试,但是下次您需要修改功能时,建议您尝试为该模块或类编写测试,在应用任何更改之前,覆盖率应达到70%以上。看看是否对您有帮助。

如果您尝试一下并且可以诚实地说这没有帮助,那么就不够公平了,但是我认为有足够的行业证据表明,他们至少使您在尝试该方法时值得这样做。 >

评论


我喜欢关于防止回归和增加信心的想法。这些正是我要添加测试的原因。

– Ken Pespisa
2011年4月8日在16:44

#12 楼

我遇到的两个非常好的答案在这里:



什么时候进行单元测试与手动测试
关于单元测试,什么不应该测试?
避免明显开销的理由:


为您的公司立即节省时间/成本
从长远来看,在故障排除/维护/扩展方面可能节省时间/成本即使离开后也是如此。

您是否不想离开自己身边的优质产品作为工作质量的证明?用自私的话来说,这样做对您来说不是更好吗?

评论


最后的好问题。我绝对为自己的工作感到自豪,并且我的应用程序运行良好(如果我可能这么胆大的话)。但是您是对的-在某些测试的支持下它们可能会更好。我认为我的主要目的是尝试在必须从事该项目的时间内尽可能多地进行有用的测试,而又不会对代码覆盖率产生过多的关注。

– Ken Pespisa
2011年4月8日在17:01

#13 楼

似乎大多数答案都是亲TDD的,即使问题不是关于TDD的,而是关于单元测试的一般性的问题。进行单元测试。但是有时候似乎很多程序员都不进行单元测试:


私有方法

根据您的OOP原理,您可能会创建私有方法使复杂的例程与您的公共方法脱钩。公共方法通常旨在在许多不同的地方被调用并经常使用,而私有方法仅由类或模块中的一个或两个公共方法真正地调用,以实现非常特定的功能。通常,为公共方法编写单元测试就足够了,但对于使某些魔术发生的底层私有方法而言,就足够了。如果私有方法出了问题,那么您的公共方法单元测试应该足以检测到这些问题。 br />
许多新的程序员在初次学习测试时就与之相反,认为他们需要对正在执行的每一行进行测试。如果您使用的是外部库,并且其功能已经过作者的充分测试和记录,则在单元测试中测试特定功能通常毫无意义。例如,某人可能编写测试以确保其ActiveRecord模型通过数据库的“ before_save”回调对属性保留正确的值,即使该行为本身已经在Rails中进行了全面测试。回调可能正在调用的方法,但不是回调行为本身。导入库的任何潜在问题最好通过验收测试而不是单元测试来揭示。

无论您是否进行TDD,这两种方法都适用。

#14 楼

肯,
我和许多其他开发人员在我们的职业生涯中多次得出与您相同的结论。 )是为应用程序编写测试的初期投资似乎令人望而生畏,但如果编写得当并且针对代码的正确部分,它们确实可以节省大量时间。

是可用的测试框架。我从来没有真正感觉到他们就是我想要的东西,所以我只是推出了自己的非常简单的解决方案。这确实使我了解了回归测试的“阴暗面”。我将分享我在这里所做的操作的基本伪代码片段,希望您能找到适合您的解决方案。

弄清楚对于您正在执行的任何项目,您认为哪种粒度级别是最佳的“物有所值”。基本面不会真正改变。

祝你好运!

评论


我认为随着时间的推移,我会弄清楚哪种粒度级别是最佳的。很高兴听到其他人定期创建测试以使他们明智地进行测试,而不是为每个可能的结果自动编写测试。我对测试的第一个介绍是在必须进行所有测试的那种级别上。实际上,TDD的整个概念似乎都遵循着这一口头禅。

– Ken Pespisa
2011年4月8日在17:08

我认为您可能会受益于使用SubSpec之类的框架(受BDD启发),因此可以在共享上下文设置的同时获得断言(“ SubTest”)隔离。

–约翰内斯·鲁道夫(Johannes Rudolph)
2011年4月13日在12:59