在进行单元测试时,100%的代码覆盖率是否真的很好?

Ars*_*nko 28 documentation tdd unit-testing code-maintainability

我总是了解到使用单元测试进行最大程度的代码覆盖是很好的.我还听到像微软这样的大公司的开发人员说他们编写了比可执行代码本身更多的测试代码.

现在,这真的很棒吗?它有时似乎完全没有时间损失,只会使维护更加困难吗?

例如,假设我有一个方法DisplayBooks()可以填充数据库中的书籍列表.产品要求表明,如果商店中有超过一百本书,则只能显示一百本.

所以,有了TDD,

  1. 我将首先进行单元测试BooksLimit(),这将在数据库中保存两百本书,调用DisplayBooks()并执行Assert.AreEqual(100, DisplayedBooks.Count).
  2. 然后我会测试它是否失败,
  3. 然后我会DisplayBooks()通过将结果限制设置为100来改变
  4. 最后,我将重新运行测试,看看它是否成功.

那么,直接进入第三步是不是更容易,而且根本不进行BooksLimit()单元测试?当需求从100个书籍限制改为200个限制,更改只有一个字符而不是更改测试,运行测试以检查它是否失败,更改代码并再次运行测试以检查是否成功时,是不是更敏捷?

注意:我们假设代码已完整记录.否则,有些人可能会说,他们是对的,进行完整的单元测试将有助于理解缺乏文档的代码.实际上,进行BooksLimit()单元测试将非常清楚地显示存在最大数量的书籍,并且该最大数量为100.进入非单元测试代码将更加困难,因为这样的限制可能是虽然for (int bookIndex = 0; bookIndex < 100; ...或实施foreach ... if (count >= 100) break;.

Ste*_*hen 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++;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在考虑我写一个测试测试:

EXPECT_EQ(3, Bar(true, true));
EXPECT_EQ(3, Bar(false, false));
Run Code Online (Sandbox Code Playgroud)

这是100%的覆盖率.这也是一个不符合合同的功能 - Bar(false, true);失败,因为它返回4.因此"完全覆盖"不是最终目标.

老实说,我会跳过测试BooksLimit().它返回一个常量,因此可能不值得花时间编写它们(并且应该在编写时进行测试DisplayBooks()).当某人决定(错误地)从货架尺寸计算该限制时,我可能会感到难过,并且它不再满足我们的要求.我以前被"不值得测试"烧伤了.去年我写了一些代码,我告诉我的同事:"这个类主要是数据,不需要测试".它有一个方法.它有一个bug.它投入生产.它在半夜打电话给我们.我觉得很蠢.所以我写了测试.然后我一直在思考什么代码构成"不值得测试".没有多少.

所以,是的,你可以跳过一些测试.100%的测试覆盖率非常好,但它并不奇怪意味着您的软件是完美的.这一切都归结为面对变革的信心.

如果我把class A,class Bclass C在一起,我找到的东西,不工作,做我想要花时间来调试所有三个?不,我想知道这一点A并且B已经满足了他们的合同(通过单元测试),我的新代码class C可能已经破了.所以我对它进行了单元测试 如果我不进行单元测试,我怎么知道它坏了?通过单击某些按钮并尝试新代码?这很好,但还不够.一旦你的程序扩展,就不可能重新运行所有的手动测试来检查一切是否正常.这就是为什么单元测试的人通常也会自动运行他们的测试.告诉我"通过"或"失败",不要告诉我"输出是......".

好的,要去写一些更多的测试......

  • 并非一切都可以通过单元测试进行测试.调试不仅仅是编写单元测试.尝试使用该软件,查找UI错误以及**人工驱动的**beta测试对于发现错误也很重要.问题是:更正常的是纠正每个用户都会看到(并且被烦恼)的图形故障,或者你可以通过单元测试找到的远程bug,但是在使用时只能看到一百万次软件?是否值得花更多的时间编写测试然后编码,当你可以花时间使用你的软件来理解改进它的地方? (3认同)
  • @nico,单元测试应该测试隔离功能,它们既不是系统测试也不是集成测试,不会替换它们.但是,通过验证代码单元的意图,它们可以确保代码的稳定整体行为,特别是在进行更改/增强时,因此也降低了更复杂的后续成本(就往返时间和工作量而言)更"昂贵"的复杂测试. (2认同)
  • @MainMa:确切地说.文件的准确性受到侵蚀,人们只是不读它 - 谁能责怪他们?"真相"在代码中!Unittests是检查这些要求的一种方式.最近,作为"新人",我的很多问题都是"我将行为从X改为Y并且没有单元测试失败.行为X是否重要?有人关心吗?".如果答案是"是",则测试不充分,所以我们添加一些. (2认同)

sor*_*oru 12

100%的单元测试覆盖率通常是代码气味,这表明某人已经通过覆盖工具中的绿色条覆盖了所有OCD,而不是做更有用的事情.

大约85%的位置是最佳位置,其中测试失败的次数更多并不表示实际或潜在的问题,而不仅仅是评论标记内的任何文本更改的必然结果.如果您的假设是"代码就是它的代码,并且它是否以任何方式不同,那么您就不会记录有关代码的任何有用的假设".这是一个由注释感知校验和工具解决的问题,而不是单元测试.

我希望有一些工具可以让你指定目标覆盖范围.然后,如果你不小心翻过它,用黄色/橙色/红色显示东西,以推动你删除一些虚假的额外测试.

  • 很公平.你指的是无意识的测试 - 这是绝对必须避免的,并且与强制执行100%规则的项目相同. (3认同)
  • 不同意(虽然不是我投票的程度).100%的代码覆盖率是理想的,但不是必需的.根据我的经验,如果程序员花费大量时间来达到100%,这可能意味着正在测试的代码需要重构.您所做的每一个假设都是一行代码,需要进行验证.只是调用这条线是不够的 - 还必须验证效果和副作用. (2认同)

Luc*_*ero 6

在查看一个孤立的问题时,你是完全正确的.但是单元测试是关于覆盖你对某段代码的所有意图.

基本上,单元测试制定了你的意图.随着意图的增加,可以始终根据目前所做的所有意图检查要测试的代码的行为.无论何时进行更改,您都可以证明没有任何副作用会破坏现有的意图.新发现的错误只不过是代码所不具备的(隐含)意图,因此您将自己的意图表达为新测试(最初失败)并修复它.

对于一次性代码,单元测试确实不值得付出努力,因为预计不会发生重大变化.但是,对于任何要维护的代码块或用作其他代码的组件,保证所有意图都适用于任何新版本都是值得的(就手动尝试检查副作用而言更省力) .

单元测试实际上节省时间和金钱的转折点取决于代码的复杂性,但总是有一个临界点,通常只需要几次迭代后就可以达到.此外,最后但并非最不重要的是,它允许您更快地发布修复和更改,而不会影响产品质量.


dzi*_*ida 5

代码覆盖率和良好的软件之间没有任何关系.您可以轻松地想象具有100%(或接近)代码覆盖率的代码段,但它仍然包含许多错误.(这并不意味着测试很糟糕!)

关于"完全没有测试"方法的敏捷性的问题是一个很好的问题,只是为了短视角(这意味着如果你打算长时间构建你的程序,这很可能是不好的).根据我的经验,我知道,当您的项目变得越来越大,并且在某个阶段您需要进行重大更改时,这些简单的测试非常有用.当你对自己说'这是一个很好的决定'这是一个很好的决定,花一些额外的时间来编写那个发现我刚才介绍的bug的小测试!".

我最近是代码覆盖的忠实粉丝,但现在它变成了(运气)类似'问题覆盖'的方法.这意味着您的测试应该涵盖所有被发现的问题和错误,而不仅仅是"代码行".没有必要进行"代码覆盖竞赛".

我将数字测试中的"敏捷"一词理解为"帮助我构建优秀软件的测试数量,而不是浪费时间编写不必要的代码",而不是"100%覆盖"或"根本没有测试".这是非常主观的,它基于您的经验,团队,技术和许多其他因素.

"100%代码覆盖率"的心理副作用是您可能认为您的代码没有错误,这从来都不是真的:)