单元测试什么

时间:2019-06-28 12:56:29

标签: java unit-testing testing junit

我有点困惑我应该在单元测试上投入多少钱。

说我有一个简单的功能,例如:

appendRepeats(StringBuilder strB, char c, int repeats)

[此函数会将字符c重复附加到strB的次数。 例如:

strB = "hello"
c = "h"
repeats = 5
// result
strB = "hellohhhhh"

]

对于此功能的单元测试,我觉得已经有很多可能性:

  • AppendRepeats_ZeroRepeats_DontAppend
  • AppendRepeats_NegativeRepeats_DontAppend
  • AppendRepeats_PositiveRepeats_Append
  • AppendRepeats_NullStrBZeroRepeats_DontAppend
  • AppendRepeats_NullStrBNegativeRepeats_DontAppend
  • AppendRepeats_NullStrBPositiveRepeats_Append
  • AppendRepeats_EmptyStrBZeroRepeats_DontAppend
  • AppendRepeats_EmptyStrBNegativeRepeats_DontAppend
  • AppendRepeats_EmptyStrBPositiveRepeats_Append
  • 等等等 strB可以为null或为空或具有值。 c可以为null或有值 重复可以是负数或正数或零

似乎已经有3 * 2 * 3 = 18种测试方法。如果其他功能还需要测试特殊字符(例如Integer.MIN_VALUE,Integer.MAX_VALUE等),那么在其他功能上可能会涉及更多内容。 我应该停止的路线是什么? 我是否应该为自己的程序做以下假设: strB只能为空或有值 c具有价值 重复只能为空或正数

很抱歉打扰。只是真正地混淆了我一般应该对单元测试多大的疑惑。我应该处于假设范围之内还是该做法不好,并且在每种情况下都应该有一种方法,在这种情况下,单元测试方法的数量将迅速成倍增长。

4 个答案:

答案 0 :(得分:3)

没有正确的答案,这是个人见解和感觉的问题。

但是,我认为有些事情是普遍的:

  • 如果您采用“测试驱动开发”,除非您首先编写了失败的单元测试,否则您从不会编写任何非测试代码,这将指导您编写的测试数量。拥有一些TDD经验,您会对此有所了解,因此,即使您需要为非TDD的旧代码编写单元测试,也可以像编写TDD一样编写测试。
  • 如果一个类有太多的单元测试,则表明该类做了太多的事情。但是,“太多”很难量化。但是当感觉太多时,请尝试将班级分为更多的班级,每个班级的职责更少。
  • 模拟是单元测试的基础-如果没有模拟协作者,您所进行的测试将不仅仅是“单元”。因此,学习使用模拟框架。
  • 检查null并测试这些检查,可能会增加很多代码。如果采用从不产生null的样式,则您的代码将永远不需要处理null,也不需要测试在这种情况下会发生什么。
    • 对此有一些例外,例如,如果您要提供库代码,并且想给调用方友好的无效参数错误
  • 对于某些方法,属性测试可能是通过许多测试命中代码的可行方法。 jUnit的@Theory是此实现的一种。它使您可以测试断言,例如'plus(x,y)对于x的正数和y的正数都返回正数。

答案 1 :(得分:1)

一般的经验法则通常是代码中的每个“ fork”都应进行测试,这意味着您应该涵盖所有可能的边缘情况。

例如,如果您具有以下代码:

if (x != null) {
  if (x.length > 100) {
    // do something  
  } else {
    // do something else
  }
} else {
  // do something completely else
}

您应该有三个测试用例-一个用于null,一个用于小于100的值,一个用于较长的值。 这是如果您很严格并且想要100%被覆盖。

无论是不同的测试还是参数化的重要性都不那么重要,而更多的是样式问题,您可以选择其中一种。我认为更重要的是覆盖所有情况。

答案 2 :(得分:1)

首先,使用代码覆盖率工具。这将向您显示测试执行的代码行。 IDE具有用于代码覆盖率工具的插件,因此您可以运行测试并查看执行了哪些行。拍摄每条线,这在某些情况下可能很难,但是对于这种实用程序来说,它是非常可行的。

使用代码覆盖率工具可以使未发现的边缘情况突出。对于难以实现的测试,代码覆盖率会向您显示测试执行的行,因此,如果测试中存在错误,您可以查看测试的执行距离。

接下来,请了解没有任何测试涵盖所有内容。总会有您不测试的值。因此,请选择感兴趣的代表性输入,并避免看起来多余的输入。例如,传递空的StringBuilder真的是您关心的事情吗?它不会影响代码的行为。有一些可能引起问题的特殊值,例如null。如果您正在测试二进制搜索,则需要覆盖数组确实很大的情况,以查看中点计算是否溢出。寻找重要的案例。

如果您预先验证并剔除麻烦的值,则无需进行过多的工作测试。一项通过测试null StringBuilder来验证是否抛出IllegalArgumentException,另一项通过测试负重复值来验证是否为此抛出异常。

最后,测试是针对开发人员的。做对您有用的事情。

答案 3 :(得分:1)

您开发的测试用例集是黑盒测试设计方法的结果,实际上,它们看起来就像您已应用了分类树方法。虽然在进行单元测试时暂时采取黑匣子的观点是完全可以的,但将自己限制在黑匣子测试中只会产生一些不良影响:首先,正如您所观察到的,您可以得到所有笛卡尔积的笛卡尔积。每个输入的可能场景,其次,您可能仍将找不到特定于所选实现的错误。

通过(也)采用玻璃盒子(又称白盒子)的观点,您可以避免创建无用的测试:知道您的代码作为第一步就可以处理重复次数为负的特殊情况,这意味着您不必不必将此情况与所有其他情况相乘。当然,这意味着您正在利用对实现细节的了解:如果以后要更改代码,以便在多个地方进行负重复检查,那么最好也调整测试套件。

由于似乎对测试实现细节有广泛的关注:单元测试就是测试实现。不同的实现有不同的潜在错误。如果您不使用单元测试来发现这些错误,那么任何其他测试级别(集成,子系统,系统)绝对不适合系统地查找它们-在更大的项目中,您不希望实现级别的错误逃脱以后的开发阶段甚至是实地。附带一提,覆盖率分析意味着您采用了玻璃盒子的观点,而TDD也是这样做的。

但是,正确的是,测试套件或单个测试不应不必要地依赖于实现细节-但这与声明您根本不应该依赖实现细节完全不同。因此,一种可行的方法是,从黑盒的角度出发,进行一系列有意义的测试,以及旨在捕获那些特定于实现的错误的测试。更改代码时需要对后者进行调整,但是可以通过多种方式来减少工作量,例如使用测试助手方法等。

在您的情况下,采用玻璃箱透视图可能会将带有负重复的测试数减少为1,同时将null字符的情况也可能减少为NullStrB的情况(假设您通过将null替换为空来尽早处理该情况)字符串),等等。