我总是了解到使用单元测试进行最大程度的代码覆盖是好。我还听到像微软这样的大公司的开发人员说他们编写了比可执行代码本身更多的测试代码。
现在,真的很棒吗?看起来有时像完全没时间这对使维护更加困难只会产生影响< / em>的
例如,假设我有一个方法DisplayBooks()
,它填充数据库中的书籍列表。产品要求告诉我们,如果商店中有超过一百本书,只能显示一百本。
所以,有了TDD,
BooksLimit()
,这将在数据库中保存200本图书,致电DisplayBooks()
,并执行Assert.AreEqual(100, DisplayedBooks.Count)
。DisplayBooks()
嗯,直接进入第三步是不是更容易,而且根本不进行BooksLimit()
单元测试?当需求从100个书籍限制改为200个限制,更改只有一个字符,而不是更改测试,运行测试以检查是否失败,更改代码并再次运行测试以检查是否成功时,是不是更敏捷?
注意:我们假设代码已完整记录。否则,有些人可能会说,他们是对的,进行完整的单元测试将有助于理解缺乏文档的代码。实际上,进行BooksLimit()
单元测试将非常清楚地显示要显示的书籍数量最多,而且这个最大数量为100.进入非单元测试代码会更加困难,因为此类限制可以通过for (int bookIndex = 0; bookIndex < 100; ...
或foreach ... if (count >= 100) break;
来实现。
答案 0 :(得分:33)
那么,直接进入第三步是不是更容易,而且根本不进行BooksLimit()单元测试?
是的......如果您没有花时间编写测试,那么您将花费更少的时间编写测试。您的项目总体上可能需要更长,因为您将花费大量时间进行调试,但可能更容易向您的经理解释?如果是这样的话......获得一份新工作!测试对提高您对软件的信心至关重要。
当您拥有大量代码时,单元测试可以提供最大的价值。使用几个类调试简单的家庭作业很容易,无需单元测试。一旦你走出这个世界,并且你在数百万行的代码库中工作 - 你就会需要它。你根本无法通过一切单步执行调试器。你根本无法理解一切。你需要知道你依赖于工作的课程。你需要知道是否有人说“我只是要对这种行为进行改变......因为我需要它”,但他们忘记了其他两百种用途依赖于这种行为。单元测试有助于防止这种情况发生。
关于使维护变得更难:没有办法!我无法将这一点弄清楚。
如果你是唯一曾参与过你项目的人,那么,你可能会想到这一点。但这是疯狂的谈话!尝试在没有单元测试的情况下快速完成30k线路项目。尝试添加需要对代码进行重大更改的功能,而无需进行单元测试。 没有信心你没有打破其他工程师的隐含假设。对于维护者(或现有项目的新开发人员),单元测试是关键。我倾向于使用单元测试来获取文档,行为,假设,告诉我何时破坏了某些东西(我认为这是无关的)。有时,编写得很糟糕的API编写的测试写得不好,可能会成为改变的噩梦,因为测试会耗费你所有的时间。最终,您将要重构此代码并进行修复,但您的用户也会对此表示感谢 - 因为它,您的API将更容易使用。
关于报道的说明:
对我来说,这不是100%的测试覆盖率。 100%覆盖率找不到所有错误,考虑使用两个if
语句的函数:
// Will return a number less than or equal to 3
int Bar(bool cond1, bool cond2) {
int b;
if (cond1) {
b++;
} else {
b+=2;
}
if (cond2) {
b+=2;
} else {
b++;
}
}
现在考虑我编写一个测试测试:
EXPECT_EQ(3, Bar(true, true));
EXPECT_EQ(3, Bar(false, false));
这是100%的报道。这也是一个不符合合同的函数 - Bar(false, true);
失败,因为它返回4.所以“完全覆盖”不是最终目标。
老实说,我会跳过BooksLimit()
的测试。它返回一个常量,因此可能不值得花时间编写它们(并且应该在编写DisplayBooks()
时进行测试)。当某人决定(错误地)从货架尺寸计算该限制时,我可能会感到难过,并且它不再满足我们的要求。我以前被“不值得测试”烧伤了。去年我写了一些代码,我告诉我的同事:“这个类主要是数据,不需要测试”。它有一个方法。它有一个bug。它投入生产。它在半夜打电话给我们。我觉得很蠢。所以我写了测试。然后我一直在思考什么代码构成“不值得测试”。没有多少。
所以,是的,你可以跳过一些测试。 100%的测试覆盖率非常好,但它并不奇怪意味着您的软件是完美的。这一切都归结为面对变革的信心。
如果我将class A
,class B
和class C
放在一起,并且我找到了一些不起作用的内容,我是否想花时间调试这三个?不。我想知道A
和B
已经达到了他们的合同(通过单元测试),我class C
中的新代码可能已被破坏。所以我对它进行了单元测试如果我不进行单元测试,我怎么知道它坏了?通过单击某些按钮并尝试新代码?这很好,但还不够。一旦你的程序扩展,就不可能重新运行所有的手动测试来检查一切是否正常。这就是为什么单元测试的人通常也会自动运行他们的测试。告诉我“通过”或“失败”,不要告诉我“输出是......”。
好的,要再写一些测试......
答案 1 :(得分:12)
100%的单元测试覆盖率通常是一种代码气味,这表明某人已经通过覆盖工具中的绿色条覆盖所有OCD,而不是做一些更有用的事情。
大约85%的地方是最佳点,其中测试失败的次数更多并不表示实际或潜在的问题,而不仅仅是评论标记内部任何文本更改的必然结果。如果您的假设是“代码就是它的代码,并且它是否以任何方式不同,那么您就不会记录有关代码的任何有用的假设”。这是一个由注释感知校验和工具解决的问题,而不是单元测试。
我希望有一些工具可以让你指定目标覆盖范围。然后,如果你不小心翻过它,用黄色/橙色/红色显示东西,推动你删除一些虚假的额外测试。
答案 2 :(得分:6)
当看到一个孤立的问题时,你是完全正确的。但单元测试是关于覆盖你对某段代码的所有意图。
基本上,单元测试表达了你的意图。随着意图的增加,可以始终根据目前所做的所有意图检查要测试的代码的行为。无论何时进行更改,您都可以证明没有任何副作用会破坏现有的意图。新发现的错误只不过是一个(隐含的)意图,它不是代码所持有的,所以你将你的意图表达为新的测试(最初失败)并修复它。
对于一次性代码,单元测试确实不值得,因为预计不会有重大变化。但是,对于任何要维护的代码块或作为其他代码的组件,保证所有意图都适用于任何新版本都是值得的(就减少手动尝试检查副作用的努力而言)
单元测试实际上节省了时间和金钱的转折点取决于代码的复杂性,但总是有一个临界点,通常只需几次迭代后就可以达到。此外,最后但并非最不重要的是,它允许您更快地发布修复和更改,而不会影响产品质量。
答案 3 :(得分:5)
代码覆盖率和良好的软件之间没有任何关系。您可以轻松地想象具有100%(或接近)代码覆盖率的代码段,但它仍然包含许多错误。 (这并不意味着测试很糟糕!)
关于“完全没有测试”方法的敏捷性的问题仅适用于短视角(这意味着如果您计划长时间构建程序,则很可能不好)。根据我的经验,我知道,当您的项目变得越来越大,并且在某个阶段您需要进行重大更改时,这些简单的测试非常有用。当你对自己说时,这可能是一个很好的事情'这是一个很好的决定,花一些额外的时间来编写那个发现我刚刚介绍过的bug的小测试!“。
我最近是代码覆盖的忠实粉丝,但现在它变成了(幸运的)像'问题覆盖'方法。这意味着您的测试应该涵盖所有问题和错误,而不仅仅是'代码行。没有必要进行'代码覆盖竞赛'。
我在数字测试方面理解'敏捷'这个词,因为'测试数量可以帮助我构建优秀的软件,而不是浪费时间编写不必要的代码而不是'100%覆盖'或者“根本没有测试”。这是非常主观的,它基于您的经验,团队,技术和许多其他因素。
“100%代码覆盖率”的心理副作用是您可能认为您的代码没有错误,这从来都不是真的:)
答案 4 :(得分:2)
我同意@soru,100%的测试覆盖率不是一个理性的目标。
我不相信任何可以告诉您“正确”覆盖范围的工具或指标。当我在读研究生时,我的论文顾问的工作是设计“变异”代码的测试覆盖率。他接受了一系列测试,然后运行一个自动程序来测试被测源代码中的错误。我们的想法是,变异代码包含在现实世界中可以找到的错误,因此找到破损代码百分比最高的测试套件就是赢家。
虽然他的论文被接受,而他现在是一所大型工程学院的教授,但他没有找到:
1)最佳的测试覆盖率 2)任何可以找到100%错误的套件。
注意,目标是找到100%的错误,而不是找到100%的覆盖率。
@ soru的85%是否正确是一个讨论的主题。我无法评估更好的数字是80%还是90%或其他任何东西。但作为一项有效的评估,85%的人认为对我有好处。
答案 5 :(得分:2)
抱歉我的英文。
100%的代码覆盖率是一种管理生理障碍,可以人为地打动利益相关者。我们进行测试是因为存在一些可能导致缺陷的复杂代码。因此,我们需要确保复杂的代码具有测试用例,经过测试并且缺陷在其生存之前就已得到修复。
我们的目标应该是测试复杂而不仅仅是一切的东西。现在,这个复合体需要用一些度量数表示,这些度量数可以是以下的圈复杂度,代码行,聚合,耦合等,或者它可能是上述所有事物的顶点。如果我们发现该度量标准更高,我们需要确保这部分代码被覆盖。以下是我的文章,其中涵盖了代码覆盖率的最佳百分比。
答案 6 :(得分:1)
首先100%很难获得大项目!即使你在代码块被覆盖时这样做,也不意味着它正在做它应该做的事情,除非你的测试断言每一个可能的输入和输出(这几乎是不可能的)。
所以我不会认为一块软件只是因为它具有100%的代码覆盖率而且代码覆盖率仍然是一件好事。
很好地进行测试会让你非常自信,如果有人更改了代码并且测试失败,你会注意到新代码出了问题,因此你可以避免在应用程序中出现任何潜在的错误那么,直接进入第三步是不是更容易,而且根本不进行BooksLimit()单元测试?
答案 7 :(得分:0)
当客户决定将限制更改为200时,祝你好运找到与这个看似微不足道的测试相关的错误。特别是,当您的代码中有其他100个变量时,还有其他5个开发人员正在处理依赖于这一小部分信息的代码。
我的观点:如果它对商业价值有价值(或者,如果你不喜欢这个名字,那么对项目的非常重要的核心),测试一下。只有在没有可能(或便宜)测试方式时才会丢弃,例如UI或用户交互,或者当您确定不编写该测试的影响很小时。对于需要模糊或快速变化的项目[我痛苦地发现],这更为真实。
对于您提供的其他示例,建议使用测试边界值。因此,您可以将测试限制为仅四个值:0,介于0和BooksLimit之间的一些神奇数字,BooksLimit以及更高的数字。
而且,正如其他人所说,做出测试,但要100%肯定其他事情可能会失败。