破坏单元测试的语义

时间:2012-07-13 15:58:30

标签: java unit-testing junit

我正在为我们的主要产品编写单元测试并且有一个问题:如何区分

  • 测试失败,因为他们测试的内容出错了(发现了一个错误,例如它的非回归测试)
  • 测试失败,因为测试的另一个意外部分失败(因为测试错误或出现未知错误)

对于第一个,我们肯定有jUnit Assert框架,但是对于第二个框架我们有什么?

示例:我的单元测试是测试c()不会抛出MyException,但是为了执行c()我需要首先执行a()然后b()这两个都可以抛出MyException(),所以我会写:

@Test
public void testC() {
  a();
  Object forC = b();
  try {
    c(forC);
  } catch (MyException e) {
    Assert.fail("....");
  }
}

但是我需要处理可以被a或b抛出的MyException,并且还要处理forC不应该为null的事实。这样做的最佳方式是什么?

  • 捕获由a或b和Assert.fail抛出的MyException,但是a和b未经过此测试的测试,因此对我来说,它们在失败时不应被标记为测试失败。也许他们以后失败了,因为此时我们应该做b(); a()而不是a(); b();.
  • 让testC抛出MyException,以便测试将以“MyException”失败,但这是误导性的,因为MyException不会告诉测试编写错误。然后所有测试都将失败,每个测试都有自己的异常。在这种情况下,如果forC为null,我也需要抛出类似NullPointerException的东西,它也没有语义。
  • 抓住并将由a和b抛出的MyException包装成一个异常,告诉测试可能是错误的,就像TestCorruptedException一样。但我无法在jUnit中找到这样的异常,因此jUnit不会识别它们(这对我来说没问题)。此外,我需要从我的所有单元测试中了解此异常,这些测试当然分为多个模块,项目等等......所以这是可行的,但增加了依赖性。

你对这个问题的解决方案是什么?我可能会选择第二个,但我对此并不满意,如上所述。

5 个答案:

答案 0 :(得分:5)

单元测试的基石是测试所需的最少代码量,否则它可能会落入您正在寻找端到端功能的集成测试空间。

如果你可以证明a()可以在它自己的测试类中创建和测试,并且b()也可以在它自己的测试类中创建和测试,那么它就是你的测试可以忽略a()b()的测试,而是使用不会失败的已知值。通常使用mock objects来满足这一要求。

将()和b()创建为模拟对象,可以单独测试c()。如果您发现无法单独测试c()和()和b(),则表明您的代码需要更改,以便关注点分离。这通常由a {)的injecting the dependany和c()的b()来满足。

when to use mock objects in unit testing上的这篇文章可能有助于更多地了解这一主题。

答案 1 :(得分:1)

明智的一句话 - 永远不要在单元测试中这样做

 try {
    c(forC);
  } catch (MyException e) {
    Assert.fail("....");
  }

对于任何需要处理代码的人来说,这会产生非常烦人的副作用:异常的原因会丢失。这意味着修复开发人员将进入并调试测试或删除try / catch块以查看异常的根本原因。

如果测试抛出异常而不应该,只是让它。测试将失败,测试运行开发人员可以在控制台/日志中轻松查看导致异常的原因。听取@piotrek给出的建议,让你的测试尽可能简单。我无法计算有多少次我必须通过畸形测试来消除这种结构。

答案 2 :(得分:0)

着名的问题:谁来测试我们的测试?简短的回答:你无法区分。您总是可以定义测试之间的依赖关系(在某些测试框架中)。当一个测试失败时,所有依赖它的测试都不会被执行。你永远不会知道你的测试代码是否正确。这就是为什么它应该尽可能简单。它也应该在你修复代码之前执行 - 这样你可以避免在你认为你的代码被修复而实际上是测试通过的情况,因为它是错误的。

答案 3 :(得分:0)

很酷的问题! :)

我编写的每个单元测试都遵循以下结构:

// Given
 setup for the test

// When
what i specifically test

// Then
my assertions

在你的例子中,我假设在尝试之前的是设置。你需要打电话给你的考试做准备,这没关系。

但如果你做得好,a();和b();在别处测试。因此,如果其中一种方法存在错误,其他测试,他们的测试将会中断。这就是你如何区分其中断的地方。

给定和然后应该永远不会破坏。你正在测试何时。

如果您不确定a()和b(),请嘲笑它们。

最后一种情况,如果要区分异常,请断言消息。

答案 4 :(得分:0)

只是不要捕获异常,而是从测试方法中抛出异常。 JUnit将指出测试失败和测试错误之间的区别:

public void test() throws MyException {
    boolean result = instance.doStuff();
    Assert.true(result);
}

当您预期出现异常时,请执行以下操作:

public void test() {
    try {
        instance.doStuff();
        Assert.fail("Expected MyException");
    }
    catch(MyException e) {
        // expected
    }
}