2012-07-13 79 views
4

我写单元测试我们的主要产品,并有一个担忧:如何区分语义对损坏的单元测试

  • 测试失败,因为他们测试了错误(错误被发现和非回归测试它的实例)
  • 测试失败,因为测试的另一个意想不到的部分失败(因为测试是错误的或未知的bug出现)

这是第一个,我们对JUnit的断言框架当然,但是我们对第二个有什么?例如:我的单元测试正在测试c()不会抛出MyException,而是执行c()我需要先执行一个()然后b(),它们都可以抛出MyException(),所以我会写:

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

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

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

你会怎样解决这个问题?如上所述,我可能会参加第二场比赛,但我对此并不满意。

回答

5

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

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

使用作为模拟对象创建的()和b(),可以单独测试c()。如果你发现不可能在()和b()的隔离中测试c(),那么这是你的代码需要改变的一个指标,以便关注点分离。这通常由()和b()到c()的injecting the dependany满足。

when to use mock objects in unit testing上的这篇文章可能会帮助我们更多地了解这个主题。

+0

正确的,我的问题是确实是c是紧密相连的一个()因为它们的真名是startTransaction()和commit(),所以它们具有公共结构和锁。你的回答告诉我,我正在进行集成测试,所以我注定要失败,只能祈求a和b是正确的。它太过努力让他们独立,但这对我来说是解决问题的方法。 – jolivier 2012-07-13 17:00:54

+0

这就是像[Spring的注解驱动的事务管理](http://static.springsource.org/spring/docs/3.0.x/reference/transaction.html#transaction- declarative-annotations)这样的声明式事务管理真正发光的地方。您的方法c()可以抛弃对startTransaction()和commit()的调用,让您可以自由地对其进行测试。 c()不会在意它是否在事务中,你会在调用方法中捕获任何异常。这里是一个[小型教程](http://www.javacodegeeks.com/2011/09/spring-declarative-transactions-example.html)感兴趣 – Brad 2012-07-13 20:36:34

0

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

0

很酷的问题! :)

每个单元测试我写遵循此结构:

// Given 
setup for the test 

// When 
what i specifically test 

// Then 
my assertions 

在你的榜样,我认为什么是之前的尝试是建立。你需要打电话给你的测试做准备,没关系。

但如果你做得好,a();和b();在别处进行测试。所以如果在这些方法中有一个错误,其他测试,他们的测试将会中断。这就是你如何区分它突破的地方。

给定和然后应该永远不会中断。你正在测试什么时候。

如果你不确定()和b(),嘲笑他们。

而且,最后一种情况下,如果您想区分例外情况,请断言该消息。

1

一种明智的字 - 永远不会做这在单元测试

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

这对任何人谁永远不会有对你的代码工作真的很烦人的副作用:的初始原因异常丢失。这意味着修复开发者将进入并调试测试或移除try/catch块来查看异常的根本原因。

如果测试引发异常,应该不会,只是让它。测试将失败,测试运行的开发人员可以很容易地在控制台/日志中看到首先导致异常的原因。接受@piotrek提供的建议,尽可能简化测试。我不能指望我有多少次不得不从格式错误的测试中获得这个构造。

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 
    } 
} 
+0

您预期的异常方法有点过时,请使用@Test(预期= SomeException.class)用于期望的异常,其代码少且易出错--OP使用JUnit 4。 – 2012-07-20 15:47:47