3

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

  • 因测试出错而失败的测试(例如,发现了一个错误,并且它是一个非回归测试)
  • 由于测试的另一个意外部分失败而失败的测试(因为测试错误或出现未知错误)

对于第一个,我们肯定有 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 不会识别它们(这对我来说没问题)。此外,我需要从我的所有单元测试中知道这个异常,这些单元测试当然被分成多个模块、项目等......所以这是可行的,但会增加依赖项。

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

4

5 回答 5

5

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

如果您可以证明a()可以在它自己的测试类中b()创建和测试,并且也可以在它自己的测试类中创建和测试,那么您上面的测试可以忽略测试,a()而是b()使用已知的获胜值不会失败。这通常可以通过使用模拟对象来满足。

将 a() 和 b() 创建为模拟对象,c()可以单独进行测试。如果您发现无法单独测试 c() 和 a() 和 b(),那么这表明您的代码需要更改以便分离关注点。这通常通过将a() 和 b()的依赖项注入 c() 来满足。

这篇关于何时在单元测试中使用模拟对象的帖子可能有助于进一步了解这个主题。

于 2012-07-13T16:11:22.153 回答
1

聪明人的一句话——永远不要在单元测试中这样做

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

这对于任何必须处理您的代码的人来说都有一个非常烦人的副作用:异常的原始原因丢失了。这意味着修复开发人员将进入并调试测试或删除 try/catch 块以查看异常的根本原因。

如果测试抛出异常并且不应该,就让它。测试将失败,测试运行的开发人员可以很容易地在控制台/日志中首先看到导致异常的原因。采纳@piotrek 的建议,让您的测试尽可能简单。我数不清有多少次我不得不从格式错误的测试中剔除这个结构。

于 2012-07-13T16:15:44.907 回答
0

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

于 2012-07-13T16:05:29.003 回答
0

很酷的问题!:)

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

// Given
 setup for the test

// When
what i specifically test

// Then
my assertions

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

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

Given 和 Then 永远不应该中断。您正在测试何时。

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

而且,最后一种情况,如果您想区分异常,请断言消息。

于 2012-07-13T16:14:22.107 回答
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
    }
}
于 2012-07-13T19:02:04.653 回答