我应该测试(复制)数据,还是仅测试行为?

时间:2017-11-13 09:06:22

标签: java unit-testing junit automated-tests

从设计的角度来看,我想知道我应该测试数据,特别是如果它是一个众所周知的数据(不是非常可配置的) - 这可以应用于流行的文件扩展名,特殊IP等地址等。

假设我们有紧急电话号码分类器:

public class ContactClassifier {

    public final static String EMERGENCY_PHONE_NUMBER = "911";

    public boolean isEmergencyNumber(String number) {
        return number.equals(EMERGENCY_PHONE_NUMBER);
    }
}

我应该这样测试(“911”重复):

@Test
public testClassifier() {
    assertTrue(contactClassifier.isEmergencyNumber("911"));
    assertFalse(contactClassifier.isEmergencyNumber("111-other-222"));
}

或(测试是否正确识别“已配置”号码):

@Test
public testClassifier() {      
    assertTrue(contactClassifier.isEmergencyNumber(ContactClassifier.EMERGENCY_PHONE_NUMBER));
    assertFalse(contactClassifier.isEmergencyNumber("111-other-222"));
}

或在构造函数中注入“911”,看起来对我来说最合理,但即使我这样做 - 如果组件被实例化,我应该为“应用程序粘合剂”编写一个测试有适当的价值?如果有人可以在数据(代码)中输入拼写错误,那么我认为没有人可以在测试案例中做错字(我打赌这样的数据会被复制粘贴)

6 个答案:

答案 0 :(得分:4)

外部常量本身存在问题。导入消失,常量被添加到类'恒定的池。因此,当将来在原始类中更改常量时,编译器不会在.class文件之间看到依赖关系,并在测试类中保留旧的常量值。

所以你需要一个干净的构建。

此外,测试应该简短,阅读清晰,写入速度快。测试处理具体的数据案例。抽象是适得其反的,甚至可能导致测试本身的错误。 常数(如速度限制)应该刻在石头上,应该是文字。 价值属性就像汽车品牌的最大速度可以源于某种表格查找。

当然,重复的值可以放在 local 常量中。防止拼写错误,简单 - 作为本地抽象,澄清了价值的语义。

然而,由于一般情况下使用常数可能是两次或三次(正面和负面测试),我会选择裸常量

答案 1 :(得分:4)

在我看来,测试应该检查行为而不是内部实现。 isEmergencyNumber验证在您尝试测试的类中声明的常量的数量这一事实是对内部实现的验证。你不应该在测试中依赖它,因为它不安全。

让我举几个例子:

示例#1 :有人错误地更改了EMERGENCY_PHONE_NUMBER并没有注意到。第二次测试永远不会发现它。

示例#2 :假设不是非常聪明的开发人员将ContactClassifier更改为以下代码。当然,它完全是边缘情况,很可能在实践中永远不会发生,但它也有助于理解我的意思。

public final static String EMERGENCY_PHONE_NUMBER = new String("911");

public boolean isEmergencyNumber(String number) {
    return number == EMERGENCY_PHONE_NUMBER;
}

在这种情况下,您的第二次测试不会失败,因为它依赖于内部实现,但是您检查真实单词行为的第一个测试将会解决问题。

答案 2 :(得分:4)

您可以测试测试数据的重点是什么? 常量值实际上是常量值?它已经在代码中定义。 Java确保该值实际上是值,所以不要打扰。

在单元测试中应该做的是测试实现,如果它是正确的。要测试不正确的行为,请使用test中定义的数据,标记为错误,然后发送到方法。要测试数据是否正确,您可以在测试期间输入数据,如果它的边界值不为人所知,或者如果它们已在某处定义,则使用应用程序范围内的已知值(接口内的常量)。

困扰你的是,每个人都应该熟悉的数据都被置于测试中,而这根本不正确。你可以做的是将它移动到接口级别。这样,通过设计,您可以将应用程序已知数据设计为合同的一部分,并由java编译器检查其正确性。

不应检查众所周知的值,但应由某种接口处理以维护它们。更改它很容易,是的,并且您的测试不会在更改期间失败,但为了避免与它发生意外,您应该具有与它们相关联的合并请求,评论和任务。如果有人确实意外地更改了它,您可以在代码审查中找到它。如果你承诺掌握一切,你就会遇到比双常定义的常数更大的问题。

现在,在其他方法中打扰你的部分:

1)如果有人可以在数据(代码)中输入拼写错误,那么我认为没有人可以在测试案例中做错字(我打赌这样的数据会被复制粘贴)

实际上,如果有人更改了数据中的值然后继续开发,那么在某些时候他将运行clean-install并查看那些失败的测试。此时他可能会改变/忽略测试以使其通过。如果你有人随意改变数据你会有更大的问题,如果没有,并且改变是由任务定义的 - 你让某人做了两次更改(至少?)。没有专业人士和许多缺点。

2)担心有人犯错误通常是不好的做法。你无法使用代码捕获它。代码审查就是为此而设计的。您可以担心有人没有正确使用您定义的界面。

3)我应该这样测试一下:

@Test
public testClassifier() {
    assertTrue(contactClassifier.isEmergencyNumber(ContactClassifier.EMERGENCY_PHONE_NUMBER));
    assertFalse(contactClassifier.isEmergencyNumber("111-other-222"));
}

也不是这样。这不是测试而是测试批次,即在相同方法中的多次测试。它应该是这种方式(约定):

@Test
public testClassifier_emergencyNumberSupplied_correctnessConfirmed() {
    assertTrue(contactClassifier.isEmergencyNumber(ContactClassifier.EMERGENCY_PHONE_NUMBER));
}


@Test
public testClassifier_incorrectValueSupplied_correctnessNotConfirmed() {
    assertFalse(contactClassifier.isEmergencyNumber("111-other-222"));
}

4)当方法被正确命名时没有必要,但如果它足够长,你可以考虑命名test中的值。例如

@Test
public testClassifier_incorrectValueSupplied_correctnessNotConfirmed() {
    String nonEmergencyNumber = "111-other-222";
    assertFalse(contactClassifier.isEmergencyNumber(nonEmergencyNumber));
}

答案 3 :(得分:3)

编写单元测试有一个重要目的:指定要测试的方法遵循的规则。 因此,当该方法破坏该规则即行为改变时,测试将失败。 我建议用人类语言写下你想要的规则,然后用计算机语言写出来。 让我详细说明一下。

选项1 当我问ContactClassifier.isEmergencyNumber方法时,“字符串”911“是紧急号码吗?”,应该说是。 转换为

assertTrue(contactClassifier.isEmergencyNumber("911")); 

这意味着您想要控制和测试常量ContactClassifier.EMERGENCY_PHONE_NUMBER指定的数字。它的值应该是911,并且方法isEmergencyNumber(String number)对这个“911”字符串做出逻辑。

选项2 当我问ContactClassifier.isEmergencyNumber方法,ContactClassifier.EMERGENCY_PHONE_NUMBER中指定的字符串是紧急号码吗?”时,应该说是

转换为

assertTrue(contactClassifier.isEmergencyNumber("911")); 

这意味着您不关心常量ContactClassifier.EMERGENCY_PHONE_NUMBER指定的字符串。只是方法isEmergencyNumber(String number)对该字符串做了逻辑。

因此,答案取决于您希望确保上述哪一项行为。

答案 4 :(得分:2)

我选择了

@Test
public testClassifier() {
    assertTrue(contactClassifier.isEmergencyNumber("911"));
    assertFalse(contactClassifier.isEmergencyNumber("111-other-222"));
}

因为这不会测试可能有问题的被测试类中的某些内容。使用

进行测试
@Test
public testClassifier() {      
    assertTrue(contactClassifier.isEmergencyNumber(ContactClassifier.EMERGENCY_PHONE_NUMBER));
    assertFalse(contactClassifier.isEmergencyNumber("111-other-222"));
}
如果有人在ContactClassifier.EMERGENCY_PHONE_NUMBER中引入拼写错误,

将永远不会发现。

答案 5 :(得分:-2)

在我看来,没有必要测试这个逻辑。原因是:这个逻辑对我来说是微不足道的。 我们可以测试我们代码的所有部分,但我认为这样做不是一个好主意。例如getter和setter。如果我们按照理论来测试所有代码行,我们必须为getter和setter中的每一个编写测试。但是这些测试的价值很低,而且需要花费更多的时间来编写和维护。这不是一项好的投资