与测试的实际代码相比,我发现测试更加棘手,更难编写。对于我来说,花更多的时间编写测试而不是测试代码并不稀奇。

这是正常的还是我做错了什么?

问题“是单元吗?测试或测试驱动开发值得吗?”,“我们花更多的时间实施功能测试而不是实施系统本身,这正常吗?”他们的答案更多地是关于测试是否值得(例如“我们应该完全跳过编写测试吗?”中的内容)。虽然我确信测试很重要,但是我想知道我在测试上花费的时间是否比实际代码多还是正常的?或者只是我一个人。

根据视图,答案和支持我的意见来判断收到的问题,我只能认为它是网站上其他任何问题都没有解决的合法问题。

评论

轶事,但是我发现我在TDD上花费的时间与编写代码所花的时间差不多。我花了很多时间在测试上而不是代码后,才开始编写测试。

与编写代码相比,您花费的时间也更多。

同样,测试是实际的代码。您只是不向客户运送该零件。

理想情况下,与编写代码相比,您将花费更多的时间来运行。 (否则,您只需要手动完成任务即可。)

@RubberDuck:相反的经验。有时,当我根据事实编写代码和设计时,已经很整洁了,因此我不需要过多地重写代码和测试。因此,编写测试所需的时间更少。这不是一个规则,但是它经常发生在我身上。

#1 楼

我记得在软件工程课程中,一个人花了大约10%的开发时间来编写新代码,而另外90%的时间是调试,测试和文档编写。

由于单元测试捕获了调试信息,并测试对(可能是自动化的)代码的努力,将更多的精力投入其中是有意义的;实际的时间不应该超过不编写测试的调试和测试所需的时间。

最后,测试还应该兼作文档!一个人应该以使用代码的方式编写单元测试。也就是说,测试(和用法)应该很简单,将复杂的内容放入实现中。

如果很难编写测试,那么测试的代码可能很难使用!

评论


现在可能是研究代码为何如此难以测试的好时机:)尝试“展开”并非严格必要的复杂多功能行,例如大量嵌套的二进制/三元运算符...我真的很讨厌不必要的二进制文件/ ternary运算符,也具有二进制/三进制运算符作为路径之一...

–尼尔森
2015年10月14日,下午1:46

我不同意最后一部分。如果您希望获得很高的单元测试覆盖率,则需要涵盖很少见的用例,有时甚至完全违反代码的预期用途。为那些极端情况编写测试可能只是整个任务中最耗时的部分。

– otto
15年10月14日在8:05

我在其他地方已经说过了,但是单元测试往往会运行更长的时间,因为大多数代码倾向于遵循一种“帕累托原理”模式:您可以用大约20%的代码来覆盖大约80%的逻辑。覆盖100%的逻辑(即覆盖所有边缘情况大约需要五倍的单元测试代码)。当然,根据框架的不同,您可以为多个测试初始化​​环境,从而减少所需的总体代码,但即使这样,也需要进行额外的计划。接近100%的置信度比简单测试主要路径需要更多的时间。

– phyrfox
2015年10月14日在20:13

@phyrfox我认为这太谨慎了,它更像是“其他99%的代码是边缘情况”。这意味着其他99%的测试是针对那些极端情况的。

–Móż
15年10月14日在20:57

@Nelson我同意很难读取嵌套三元运算符,但是我认为它们不会使测试特别困难(一个好的覆盖率工具会告诉您是否错过了可能的组合之一)。 IMO,如果软件耦合太紧密,或者依赖于硬连线数据或未作为参数传递的数据(例如,当条件取决于当前时间而没有作为参数传递时),则很难进行测试。这与代码的“可读性”没有直接关系,当然,在所有其他条件相同的情况下,可读性更好!

– Andres F.
2015年10月15日15:49

#2 楼



即使只进行单元测试,在测试中包含比实际测试的代码更多的代码也很常见。没问题。

考虑一个简单的代码:

 public void SayHello(string personName)
{
    if (personName == null) throw new NullArgumentException("personName");

    Console.WriteLine("Hello, {0}!", personName);
}
 


将进行哪些测试?这里至少要测试四个简单的情况:


人名是null。实际抛出异常了吗?至少要编写三行测试代码。
人名是"Jeff"。我们得到"Hello, Jeff!"作为回应吗?那是四行测试代码。
人名是一个空字符串。我们期望什么输出?实际输出是多少?附带问题:是否符合功能要求?这意味着要进行单元测试的另外四行代码。
人名对于一个字符串来说足够短,但是又太长而无法与"Hello, "和感叹号结合使用。会发生什么?¹

这需要大量测试代码。此外,最基本的代码片段通常需要设置代码,该代码会初始化被测代码所需的对象,这通常还会导致编写存根和模拟等。

如果比率很大,在这种情况下,您可以检查以下几件事:测试之间是否存在代码重复?测试代码这一事实并不意味着该代码应该在相似的测试之间重复(复制粘贴):这样的重复将使这些测试的维护变得困难。
是否存在冗余测试?根据经验,如果删除单元测试,则分支覆盖率应降低。如果不是,则可能表明不需要测试,因为这些路径已经被其他测试覆盖。
您是否仅在测试应该测试的代码?您不应该测试第三方库的基础框架,而只能测试项目本身的代码。

通过烟雾测试,系统和集成测试,功能和验收测试以及压力和负载测试,您甚至可以添加更多的测试代码,因此不必为实际代码的每个LOC拥有四个或五个LOC测试。 br />
关于TDD的说明

如果您担心测试代码所花费的时间,则可能是您做错了,即代码优先,测试后来。在这种情况下,TDD可能会通过鼓励您在15-45秒的迭代中进行工作(在代码和测试之间进行切换)来提供帮助。根据TDD的支持者,它通过减少所需的测试数量,更重要的是,减少了为测试编写和尤其重写的业务代码的数量,从而加快了开发过程。


¹令n为字符串的最大长度。我们可以调用SayHello并通过引用传递长度为n-1的字符串,该字符串应该可以正常工作。现在,在Console.WriteLine步骤中,格式化应以长度为n + 8的字符串结尾,这将导致异常。可能由于内存限制,即使是包含n / 2个字符的字符串也会导致异常。一个人应该问的问题是,第四项测试是否是单元测试(看起来像单元测试,但与平均单元测试相比,它在资源方面的影响可能更大),以及它是否测试实际的代码或基础框架。 br />

评论


不要忘记一个人也可以使用null。 stackoverflow.com/questions/4456438/…

–psatek
15年10月14日在14:17

@JacobRaihle我假设@MainMa表示personName的值适合字符串,但是personName的值加上串联值会溢出字符串。

– woz
2015年10月14日17:16

@JacobRaihle:我编辑了答案以解释这一点。参见脚注。

– Arseni Mourzenko
2015年10月14日在17:22

根据经验,如果删除单元测试,则分支覆盖率应降低。如果我写了上面提到的所有四个测试,然后删除了第三个测试,覆盖率会降低吗?

– Vivek
2015年10月14日在17:28

“足够长”→“太长”(在第4点中)?

–PaŭloEbermann
15年10月14日在19:04

#3 楼

我认为区分两种类型的测试策略非常重要:单元测试和集成/验收测试。

尽管在某些情况下必须进行单元测试,但往往过分地完成了。强加给开发人员的毫无意义的指标(例如“ 100%覆盖率”)加剧了这种情况。 http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf为此提供了令人信服的论点。请考虑以下有关积极的单元测试的问题:



大量的无意义的测试不能证明业务价值,而仅仅是为了接近100%的覆盖率而存在。在我工作的地方,我们必须为工厂编写单元测试,该单元测试除了创建类的新实例外什么也不做。它没有任何价值。或由Eclipse生成的冗长的.equals()方法-无需进行测试。
为了使测试更容易,开发人员将复杂的算法细分为较小的可测试单元。听起来像是一场胜利,对吧?如果需要打开12个类来遵循一条通用代码路径,则不需要。在这些情况下,单元测试实际上会降低代码的可读性。与此相关的另一个问题是,如果将代码切得太小,最终将导致大量的类(或代码段),除了作为另一段代码的子集之外,似乎没有任何道理。 />重构高度覆盖的代码可能很困难,因为您还需要保持大量依赖于它的单元测试。行为单元测试会加剧这种情况,在行为测试中,您的部分测试还会验证类的协作者(通常是被嘲笑)的交互。

集成/验收测试是非常重要的一部分软件质量方面的知识,以我的经验,您应该花费大量时间来使它们正确。

许多商店都喝醉了TDD酷玩乐队,但正如上面的链接所示,许多研究表明,其好处尚无定论。

评论


重构点+1。我曾经研究过经过十多年修补和融合的旧产品。仅尝试确定特定方法的依赖项可能会花费一天的大部分时间,然后尝试弄清楚如何模拟它们可能会花费更长的时间。五行更改需要200行以上的测试代码,并花费一周的大部分时间,这并不罕见。

– TMN
2015年10月14日17:39

这个。在MainMa的答案测试4中(在学术环境之外)不应该进行测试,因为考虑一下在实践中将如何发生……如果一个人的名字接近某个字符串的最大大小,那就出了问题。不要测试,在大多数情况下,没有代码路径可以检测到它。适当的响应是让框架抛出底层内存不足异常,因为这是实际的问题。

–Móż
2015年10月14日在20:59

我一直在为您加油,直到“无需测试Eclipse生成的那些冗长的.equals()方法”。我为equals()和compareTo()编写了一个测试工具。github.com/GlenKPeterson/TestUtils几乎没有我测试过的所有实现。如果equals()和hashCode()无法正确有效地协同工作,如何使用集合?对于您的其余回答,我再次欢呼雀跃,并对其进行了投票。我什至同意某些自动生成的equals()方法可能不需要测试,但是我遇到了许多错误的错误代码,这些错误的实现使我感到不安。

– GlenPeterson
2015年10月15日14:27

@GlenPeterson同意。我的一位同事为此目的写了EqualsVerifier。见github.com/jqno/equalsverifier

– Tohnmeister
15-10-18在17:26

@Ӎσᶎ不,您仍然必须测试不可接受的输入,这就是人们发现安全漏洞的方式。

– gbjbaanb
2015年10月20日下午13:51

#4 楼

不能一概而论。

如果我需要从基于物理的渲染中实现公式或算法,那很可能是我花了10个小时进行偏执的单元测试,因为我知道丝毫的错误或不精确可能会导致几个月后几乎无法诊断的错误。

如果我仅是想在逻辑上将一些代码行组合在一起并给它起一个名字,仅在文件范围内使用,则可能无法测试根本没有(如果您坚持为每个功能编写测试,无一例外,程序员可能会退而求其次地编写尽可能少的功能)。

评论


这是一个非常有价值的观点。这个问题需要更多的背景才能得到充分回答。

– CLF
15年10月15日在21:43

#5 楼

我发现这是最重要的部分。

单元测试并不总是与“看是否正确”有关,而是与学习有关。一旦测试了足够的东西,它就会“硬编码”到您的大脑中,最终您会减少单元测试的时间,并且发现自己编写整个类和方法时无需进行任何测试就可以完成。

这就是为什么此页面上的其他答案之一提到他们在“课程”中进行了90%的测试的原因,因为每个人都需要学习目标的注意事项。

单元测试不仅可以从根本上提高时间,还可以极大地提高您的技能,而且是再次检查自己的代码并在过程中发现逻辑错误的好方法。

#6 楼

是的,如果您谈论的是TDDing,这很正常。进行自动化测试后,可以确保所需的代码行为。首先编写测试时,请确定现有代码是否已具有所需的行为。

这意味着:


如果编写的测试失败,然后用最简单的可行方法纠正代码比编写测试短。
如果编写通过的测试,则无需编写额外的代码,实际上比编写代码花费更多的时间。
br />
(这不考虑代码重构,其目的是花费更少的时间编写后续代码。它通过测试重构来平衡,其目的是花费更少的时间编写后续测试。)

如果是在事后再写测试,也是这样,那么您将花费更多的时间:


确定期望的行为。
确定测试期望行为的方法。
满足代码依赖关系以便能够编写测试。
为失败的测试更正代码。

花很多时间才能真正编写代码。

是的,这是一种预期的措施。

#7 楼



如果您是先编写测试(TDD),则可能会花费一些时间在编写测试上,这实际上对编写代码很有帮助。 。请考虑:


确定结果和输入(参数)
命名约定
结构-放置位置。
平凡的旧思维

在编写代码后编写测试时,您可能会发现代码不容易测试,因此编写测试会更困难/需要更长的时间。

大多数程序员编写代码的时间比测试要长得多,所以我希望他们中的大多数人不会流利。另外,您还需要花更多的时间来理解和利用您的测试框架。

我认为我们需要改变思维方式,即编码需要多长时间以及涉及单元测试的方式。永远不要在短期内查看它,也永远不要只比较交付特定功能的总时间,因为您不仅要考虑编写的是更好/更少的错误代码,而且代码更容易更改并且仍然可以使用更好/更少的越野车。

在某些时候,我们所有人都只能编写出色的代码,因此某些工具和技术只能在提高技能方面提供很多帮助。如果只用激光导引锯,就好像我不能盖房子。

#8 楼


花大量的时间(如果不是更多的话)来编写测试而不是实际的代码是正常的吗?


是的。有一些警告。

首先,在大多数大商店以这种方式工作的意义上,这是“正常的”,因此即使这种方式完全被误导和愚蠢,但事实是,大多数商店的这种工作方式使其“正常”。

这样做并不是要暗示测试是错误的。我曾在没有进行测试的环境中以及在具有强迫性测试的环境中工作,但我仍然可以告诉您,即使是强迫性测试也比没有测试要好。

我还没有做TDD,(他知道,我将来可能会这样做),但是我通过运行测试而不是实际的应用程序来完成大部分的edit-run-debug周期。 ,因此自然而然地,我会在测试中投入大量精力,以避免尽可能多地运行实际的应用程序。

但是,请注意过度测试(尤其是在过度测试中)存在危险在维护测试上花费的时间。 (我主要是为了明确指出这一点而写这个答案。)

在Roy Osherove的《单元测试的艺术》(曼宁,2009年)的序言中,作者承认参加了一个项目,之所以未能成功,很大程度上是由于设计不良的单元测试所带来的巨大开发负担,而这些单元测试必须在整个开发过程中保持下去。因此,如果您发现自己花了太多时间只做维护测试而无所事事,那并不一定意味着您走对了,因为这是“正常的”。您的开发工作可能已进入一种不健康的模式,在这种模式下,可能需要对测试方法进行彻底的重新思考以保存项目。

#9 楼


花大量的时间(如果不是更多的话)来编写测试而不是实际的代码呢?





是的如果代码高度耦合(遗留,关注点分离不足,缺少依赖项注入,未开发tdd),则进行单元测试(单独测试模块)

是否可以编写集成/接受测试仅当gui代码和业务逻辑分开时,才可以通过gui代码访问要测试的逻辑


否用于编写集成/验收测试不需要与gui交互)

否,如果存在关注点分离,依赖注入,代码是由测试驱动开发的(tdd),则无法编写单元测试。