29

我一直都知道用单元测试做最大的代码覆盖率是好的。我还听到微软等大公司的开发人员说,他们编写的测试代码行数比可执行代码本身还要多。

现在,真的很棒吗?有时这似乎不是完全浪费时间,只会使维护变得更加困难吗?

例如,假设我有一个DisplayBooks()从数据库中填充书籍列表的方法。产品需求告诉我们,如果店里有一百多本书,只能展示一百本。

因此,使用 TDD,

  1. 我将从进行单元测试开始BooksLimit(),它将在数据库中保存 200 本书,调用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;

4

8 回答 8

34

好吧,直接进入第三步不是更容易,而且根本不做 BooksLimit() 单元测试吗?

是的...如果您不花时间编写测试,您将花费更少的时间编写测试。您的项目总体上可能需要更长的时间,因为您将花费大量时间进行调试,但也许这更容易向您的经理解释?如果是这样的话……找一份新工作!测试对于提高您对软件的信心至关重要。

当您拥有大量代码时,单元测试最有价值。使用几个类无需单元测试即可轻松调试简单的家庭作业。一旦你走出世界,并且在数百万行的代码库中工作 - 你将需要它。您根本无法单步调试您的调试器。你根本无法理解一切。您需要知道您所依赖的课程。您需要知道是否有人说“我只是要对行为做出这种改变......因为我需要它”,但他们忘记了还有 200 种其他用途取决于这种行为。单元测试有助于防止这种情况。

关于使维护更加困难:NO WAY! 我不能充分利用它。

如果你是唯一一个参与过你的项目的人,那么是的,你可能会这么想。但那是疯狂的谈话!尝试在没有单元测试的情况下加快 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())。当有人决定(错误地)根据货架尺寸计算该限制时,我可能会感到难过,它不再满足我们的要求。我以前被“不值得测试”烧伤。去年我写了一些代码,我对我的同事说:“这个类主要是数据,不需要测试”。它有一个方法。它有一个错误。它开始生产了。它在半夜寻呼我们。我觉得很愚蠢。所以我写了测试。然后我对哪些代码构成“不值得测试”进行了长期而艰苦的思考。没有多少。

所以,是的,你可以跳过一些测试。100% 的测试覆盖率很棒,但这并不意味着你的软件是完美的。这一切都归结为面对变化的信心。

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

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

于 2010-06-26T12:23:32.827 回答
11

100% 的单元测试覆盖率通常是一种代码味道,表明有人已经通过覆盖工具中的绿色条克服了所有的强迫症,而不是做一些更有用的事情。

大约 85% 是最佳点,测试失败的频率更高,这并不表示存在实际或潜在问题,而不仅仅是注释标记之外的任何文本更改的必然结果。如果您的假设是“代码就是它本来的样子,并且如果它有任何不同,那就是别的东西”,那么您就没有记录任何关于代码的有用假设。这是一个注释感知校验和工具解决的问题,而不是单元测试。

我希望有一些工具可以让您指定目标覆盖率。然后,如果您不小心检查了它,请以黄色/橙色/红色显示内容,以促使您删除一些虚假的额外测试。

于 2010-06-26T13:14:38.840 回答
6

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

基本上,单元测试制定了您的意图。随着越来越多的意图,要测试的代码的行为总是可以根据迄今为止所做的所有意图进行检查。每当进行更改时,您都可以证明不存在破坏现有意图的副作用。新发现的错误只不过是代码不包含的(隐式)意图,因此您将意图表述为新测试(起初失败)并修复它。

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

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

于 2010-06-26T12:05:06.733 回答
5

代码覆盖率和好的软件之间没有明确的关系。您可以很容易地想象一段代码具有 100%(或接近)的代码覆盖率,但它仍然包含很多错误。(这并不意味着测试不好!)

您关于“根本不测试”方法的敏捷性的问题仅适用于短期观点(这意味着如果您计划长时间构建程序,这很可能不好)。根据我的经验,我知道当您的项目越来越大并且在某个阶段您需要进行重大更改时,这种简单的测试非常有用。这可能是一个时刻,你会对自己说“花一些额外的时间来编写发现我刚刚介绍的错误的小测试是一个很好的决定!”

我最近是代码覆盖的忠实粉丝,但现在它(幸运地)转向了类似“问题覆盖”的方法。这意味着您的测试应该涵盖所有发现的问题和错误,而不仅仅是“代码行”。没有必要进行“代码覆盖竞赛”

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

“100% 代码覆盖率”的心理副作用是您可能认为您的代码没有错误,但事实并非如此:)

于 2010-06-26T12:17:42.773 回答
2

我同意@soru,100% 的测试覆盖率不是一个合理的目标。

我不相信存在任何可以告诉您“正确”覆盖范围的工具或指标。当我在读研究生时,我的论文顾问的工作是为“变异”代码设计测试覆盖率。他进行了一套测试,然后运行一个自动化程序以在被测源代码中出错。这个想法是变异的代码包含在现实世界中会发现的错误,因此找到最高百分比的损坏代码的测试套件是赢家。

虽然他的论文被接受了,他现在是一所主要工程学院的教授,但他也没有找到:

1) 最佳测试覆盖率的神奇数字 2) 任何可以找到 100% 错误的套件。

请注意,目标是找到 100% 的错误,而不是找到 100% 的覆盖率。

@soru 的 85% 是否正确是一个值得讨论的话题。我无法评估更好的数字是 80% 还是 90% 或其他任何值。但作为一项工作评估,85% 的人对我来说是正确的。

于 2010-06-27T04:00:37.607 回答
2

对不起我的英语不好。

100% 的代码覆盖率是一种人为地给利益相关者留下深刻印象的管理生理障碍。我们进行测试是因为存在一些可能导致缺陷的复杂代码。所以我们需要确保复杂的代码有一个测试用例,它的测试和缺陷在它的生命之前被修复。

我们的目标应该是测试一些复杂的东西,而不仅仅是所有东西。现在这个复合体需要用一些度量数来表示,这些度量数可以是以太圈复杂度、代码行数、聚合、耦合等,或者它可能是上述所有事情的顶点。如果我们发现该指标更高,我们需要确保覆盖代码的那部分。下面是我的文章,其中涵盖了代码覆盖率的最佳百分比。

真的需要 100% 的代码覆盖率吗?

于 2013-09-07T16:51:00.280 回答
1

第一个 100% 很难获得,尤其是在大型项目上!即使您在覆盖代码块时这样做,也并不意味着它正在做它应该做的事情,除非您的测试断言所有可能的输入和输出(这几乎是不可能的)。

所以我不会仅仅因为一个软件有 100% 的代码覆盖率而认为它是好的,但代码覆盖率仍然是一件好事。

好吧,直接进入第三步不是更容易,而且根本不做 BooksLimit() 单元测试吗?

在那里进行测试会让您非常有信心,如果有人更改代码并且测试失败,您会注意到新代码有问题,因此您可以避免应用程序中的任何潜在错误

于 2010-06-26T13:25:12.407 回答
0

当客户端决定将限制更改为 200 时,祝您好运找到与该看似微不足道的测试相关的错误。特别是,当您的代码中有其他 100 个变量时,并且有其他 5 个开发人员正在编写依赖于那一小段信息的代码。

我的观点:如果它对业务价值有价值(或者,如果你不喜欢这个名字,对项目的非常重要的核心),那就测试它。只有在没有可能的(或廉价的)测试方法时才丢弃,例如 UI 或用户交互,或者当您确定不编写该测试的影响很小时。对于具有模糊或快速变化的需求的项目(正如我痛苦地发现的那样),这更适用。

对于您提供的另一个示例,建议测试边界值。因此,您可以将测试限制为仅四个值:0、0 和 BooksLimit 之间的某个神奇数字、BooksLimit 以及更高的某个数字。

而且,正如其他人所说,进行测试,但要 100% 肯定其他事情可能会失败。

于 2010-06-27T04:23:54.280 回答