您的位置:首页 > 其它

JUnit重装上阵

2008-04-10 03:34 295 查看
                                                                        JUnit重装上阵                                                                         作者:Ralf Stuckert
                                                                                                               12/07/2006
 我们必须承认,JUnit是Java世界使用最广泛的(单元)测试工具.我们也有其他功能强大的测试框架,例如TestNG(它的功能非常全面),但是它们还没有被广泛的接受,JUnit却被广泛接受.在第4个版本里,Kent Beck 和 Erich Gamma引入了近几年来的第一次API修改.在2005年,当这个修改的第一个版本发布时,你在当前的工作环境很难使用它,因为缺乏工具的支持.而现在,绝大多数的工具和IDEs开始支持JUnit 4,所以现在是尝试使用它的时候了.本文描述了JUnit 3.8x和JUnit 4之间的不同. 到底有了哪些更新?让我开门见山的告诉你:JUnit 4是完全基于annotations的.哈,你现在明白了—这意味着什么?首先,你不必继承TestCase类.其次,你的测试方法名称不必非要以前缀test开始.所有这一切你必须做的是使用@Test注释来标注你的测试方法.看看下面的例子:[align=left]import junit.framework.TestCase;[/align][align=left]import org.junit.Test;[/align][align=left] [/align][align=left]public class CalculatorTest extends TestCase {[/align][align=left] [/align][align=left]    @Test[/align][align=left]    public void testadd() { [/align][align=left]        ....[/align][align=left]    } [/align][align=left]} [/align][align=left]确实,在某些情况下,不需要继承TestCase可能非常有用.但是我习惯继承的assert...方法呢?它被你使用Java5的另外一个特性给解决了,这就是static imports.[/align][align=left]import org.junit.Test;[/align][align=left]import static org.junit.Assert.*;[/align][align=left] [/align][align=left]public class CalculatorTest {[/align][align=left] [/align][align=left]    @Test[/align][align=left]    public void add() { [/align][align=left]        ...[/align][align=left]        assertEquals( 4, calculator.add( 1, 3 ) );[/align][align=left]    } [/align][align=left]}[/align][align=left]创建测试环境[/align][align=left]所以,可以看出,写一个测试和JUnit 4没有什么不同.等等,但是setUp()和tearDown()方法发生了什么变化?我们往下看,现在你可以使用@Before 和 @After来装饰所有的方法.[/align][align=left]public class CalculatorTest {[/align][align=left] [/align][align=left]    @Before[/align][align=left]    public void prepareTestData() { ... }[/align][align=left] [/align][align=left]    @After[/align][align=left]    public void cleanupTestData() { ... }[/align][align=left]}[/align][align=left]你可能使用多个@Before 和 @After方法,但是必须注意到,当它们执行的时候,没有任何的东西来说明它们的执行顺序.[/align][align=left]public class CalculatorTest {[/align][align=left] [/align][align=left]    @Before[/align][align=left]    public void prepareTestData() { ... }[/align][align=left] [/align][align=left]    @Before[/align][align=left]    public void setupMocks() { ... }[/align][align=left] [/align][align=left]    @After[/align][align=left]    public void cleanupTestData() { ... }[/align][align=left]}[/align][align=left]值得注意的是,继承的@Before 和 @After方法是对称的执行的.这意味着一个继承CalculatorTest的测试类ScientificCalculatorTest按如下顺序执行:[/align][align=left]CalculatorTest#Before[/align][align=left]ScientificCalculatorTest#Before[/align][align=left]ScientificCalculatorTest#After[/align][align=left]CalculatorTest#After[/align][align=left]有一个大多数人都忽略的特性是为整个的一组测试定义setup/teardown.当你不需要为每一个测试运行setup而又不得不花费时间的时候,这个特性特别有用,例如建立数据库连接.这可以使用@BeforeClass 和 @AfterClass标注来解决.[/align][align=left]public class CalculatorTest {[/align][align=left] [/align][align=left]    @BeforeClass[/align][align=left]    public static void setupDatabaseConnection() { ... }[/align][align=left] [/align][align=left]    @AfterClass[/align][align=left]    public static void teardownDatabaseConnection() { ... }[/align][align=left]}[/align][align=left]它和使用@Before and @After的规格一样.这意味着你可以有多个@BeforeClass 和 @AfterClass方法,同样,也可以从父类中继承得到.注意,这些方法必须是静态的.[/align][align=left]测试Exceptions[/align][align=left]检测我们的代码产生出正确的结果是一个方面,但是有关错误处理呢?当事情出错的时候,通常抛出一个exception.测试你的代码执行正确的违例情况和实际功能一样重要(或者更加重要).并且这是单元测试(框架)必须提供的一个重大的功能.你可以在一个可控的条件下(可能需要一些mocks的帮助)驱动代码到一个失败的情况里,然后测试它是否可以抛出想要的exception.在JUnit 3.8x里,测试exception的模式为:[/align][align=left]public class CalculatorTest {[/align][align=left] [/align][align=left]    public void testDivisionByZero() {[/align][align=left]        try {[/align][align=left]            new Calculator().divide( 4, 0 );[/align][align=left]        } catch (ArithmeticException e) {}[/align][align=left]    }[/align][align=left]}[/align][align=left]在JUnit 4里,你可以使用@Test标注来声明一个期望的exception:[/align][align=left]public class CalculatorTest {[/align][align=left] [/align][align=left]    @Test(expected=ArithmeticException.class)[/align][align=left]    public void testDivisionByZero() {[/align][align=left]        new Calculator().divide( 4, 0 );[/align][align=left]    }[/align][align=left]}[/align][align=left]测试Timeout[/align][align=left]单元测试可以短时间运行,使得你可以不时的运行它.但是有时你可能会有一些测试消耗更长的时间,特别是当网络连接被引入的时候.所以随时你有理由怀疑你的测试能够按时结束,你可以确定测试会在运行一段特别的时间后被取消掉.在JUnit 3.8x中,你不得不使用一个额外的库文件来达到目的,更坏的是,你可能会利用一个新的线程.现在,这个任务毫不需要费心;你所需要做的就是在@Test标注里给定一个timeout参数.[/align][align=left]@Test(timeout=5000)[/align][align=left]    public void testLengthyOperation() {[/align][align=left]        ...[/align][align=left]    }[/align][align=left]如果timeout在测试完成之前发生,你将得到适当的失败信息:[/align][align=left]java.lang.Exception: test timed out after 5000 milliseconds[/align][align=left]忽略测试[/align][align=left]可能有一些情况你希望测试运行者忽略一些测试.可能是你正在使用的第三方库的当前版本有一个bug,或者是基础框架使得你的测试不能成功运行.不管是什么原因(或多或少),在JUnit 3.8x中,你不得不将你的测试代码注销掉,或者将它们排除在测试组合之外.在JUnit 4中,你可以用@Ignore标注来装饰你的方法.[/align][align=left]public class CalculatorTest {[/align][align=left] [/align][align=left]    @Ignore("Not running because ")[/align][align=left]    @Test[/align][align=left]    public void testTheWhatSoEverSpecialFunctionality() {[/align][align=left]    }[/align][align=left]}[/align][align=left]指向@Ignore标注的文字信息会报告给测试的使用者.即使它是可选择的,你也可以提供一个关于这个测试为什么被忽略的注释,这使得你不会忘记它.TestRunner编译到Eclipse的时候通过描述删除线来标记被忽略的测试.不幸的是,注释还没有被提供.[/align][align=left]测试组合[/align][align=left]在基于annotation的JUnit 4中,你也可以发现好的suite()方法.在以前版本是这样的:[/align][align=left]public class AllTests extends TestCase {[/align][align=left] [/align][align=left]    public static Test suite() {[/align][align=left]        TestSuite suite = new TestSuite();[/align][align=left]        suite.addTestSuite(CalculatorTest.class);[/align][align=left]        suite.addTestSuite(AnotherTest.class);[/align][align=left]        return suite;[/align][align=left]    }[/align][align=left]现在是这样的:[/align][align=left]@RunWith(value=Suite.class)[/align][align=left]@SuiteClasses(value={CalculatorTest.class, AnotherTest.class})[/align][align=left]public class AllTests {[/align][align=left]...[/align][align=left]}[/align][align=left]好了,都是些什么啊?嗯,JUnit 4引入了一个hook来指定不同的测试runners.在这种情况下,我们告诉JUnit运行组合runner来执行这个测试.@SuiteClasses标注被组合runner读取,它给定属于这个组合的测试.伙计,我确定旧的组合模式好一些.可能是这样,但是使用一种新的方式来定义组合也有一个优点:你可以给定在执行第一个组合之前和最后一个测试之后的@BeforeClass 和 @AfterClass方法.有了这个,你就能定义一个组合范围的setup.[/align][align=left]参数化的测试[/align][align=left]除了组合还有另外一个特别的runner:参数化.它使得你可以使用不同的数据组运行相同的测试.让我们来通过一个例子看看:我们将为计算给定数字n的factorial的方法写一个测试:runner,JUnit 4[/align][align=left]@RunWith(value=Parameterized.class)[/align][align=left]public class FactorialTest {[/align][align=left] [/align][align=left]    private long expected;[/align][align=left]    private int value;[/align][align=left] [/align][align=left]   @Parameters[/align][align=left]    public static Collection data() {[/align][align=left]        return Arrays.asList( new Object[][] {[/align][align=left]                             { 1, 0 },   // expected, value[/align][align=left]                             { 1, 1 }, [/align][align=left]                             { 2, 2 },[/align][align=left]                             { 24, 4 },[/align][align=left]                             { 5040, 7 },[/align][align=left]                             });[/align][align=left]    }[/align][align=left] [/align][align=left]    public FactorialTest(long expected, int value) {[/align][align=left]        this.expected = expected;[/align][align=left]        this.value = value;[/align][align=left]    }[/align][align=left] [/align][align=left]    @Test[/align][align=left]    public void factorial() {[/align][align=left]        Calculator calculator = new Calculator();[/align][align=left]        assertEquals(expected, calculator.factorial(value));[/align][align=left]    }[/align][align=left]}[/align][align=left]参数化的runner所要做的是使用以@Parameters包装的方法提供的数据运行FactorialTest所有的测试(这里我们只有一个).在这种情况下,我们有5个数据项的List.每一个项由FactorialTest的构造器参数的数组组成.我们的factorial()测试将在assertEquals()中使用这些数据.最后,这意味着我们的测试将象如下数据那样运行5次:[/align][align=left]factorial#0: assertEquals( 1, calculator.factorial( 0 ) );[/align][align=left]factorial#1: assertEquals( 1, calculator.factorial( 1 ) );[/align][align=left]factorial#2: assertEquals( 2, calculator.factorial( 2 ) );[/align][align=left]factorial#3: assertEquals( 24, calculator.factorial( 4 ) );[/align][align=left]factorial#4: assertEquals( 5040, calculator.factorial( 7 ) );[/align][align=left]多功能的其他特性[/align][align=left]新的JUnit类将要包括一个新的包:org.junit.旧的测试框架由于兼容方面的原因依然包括在包junit.framework里.另外一个精巧的改变是,使用抛出(java.lang.)AssertionError而不是junit.framework.AssertionFailedError.[/align][align=left]另一个真正有用的是测试数组相等的新的断言,它首先测试数组的长度是否相等,然后使用equals()比较数组的项.[/align][align=left]assertEquals(Object[] expected, Object[] actual)[/align][align=left]JUnit 4不再支持基于UI的TestRunner,这个功能留给了IDE的开发者.但是这里仍然有命令行工具可供你手工的执行测试.你只需要调用类org.junit.runner.JUnitCore,传入你的测试类的全名:[/align][align=left]java -cp ... org.junit.runner.JUnitCore CalculatorTest AnotherTest[/align][align=left]隐蔽的惊奇之事[/align][align=left]最后,这里有一些恶作剧.当我在尝试JUnit 4的时候,我最早的一个测试是关于琐碎的Calculator.add()方法.[/align][align=left]public class Calculator {[/align][align=left] [/align][align=left]    public long add(long number1, long number2) {[/align][align=left]        return number1 + number2;[/align][align=left]    }[/align][align=left]}[/align][align=left] [/align][align=left]public class CalculatorTest {[/align][align=left] [/align][align=left]    @Test[/align][align=left]    public void add() throws Exception {[/align][align=left]        Calculator calculator = new Calculator();[/align][align=left]        assertEquals(4, calculator.add(1, 3));[/align][align=left]    }[/align][align=left]}[/align][align=left]很简单,是吧?但是当我运行测试的时候,我得到了:[/align][align=left]java.lang.AssertionError: expected:<4> but was:<4>[/align][align=left]什么?怎么会这样?这是因为自动装箱(autoboxing).在JUnit 4中,再也不需要为简单类型数据的特殊assertEquals();仅仅只有一个assertEquals(Object expected, Object actual).如果你传入两个ints,它们会被自动转化为Integer.现在,我们的问题逐渐清晰起来: Calculator.add()返回一个long类型数据,但是在默认情况下,数值的类型为int.所以通过自动装箱,我们得到如下:[/align][align=left]        assertEquals(new Integer(4), new Long(calculator.add(1, 3)));[/align][align=left]好了,这就是我们为什么没有得到想要的结果,但是这是一个问题吗?是,让我们看看实现Integer的equals():[/align][align=left]    public boolean equals(Object obj) {[/align][align=left]        if (obj instanceof Integer) {[/align][align=left]            return value == ((Integer)obj).intValue();[/align][align=left]        }[/align][align=left]        return false;[/align][align=left]    }[/align][align=left]不幸的是,我们不能将一个Integer和Long进行比较,所以equals()永远返回false.当你将你的旧的测试转移到JUnit 4的时候,这可能是一个问题.在JUnit 3.8x中,这个测试可以正确运行,因为有为各种简单类型准备的assertEquals(),所以在我们的例子中,编译器会选择assertEquals(long expected, long actual).要解决这个问题,你必须明确的使用long.[/align][align=left]assertEquals( (long)4, calculator.add(1, 3) );[/align][align=left]那又怎样?[/align][align=left]JUnit 4给我们带来了一些新的富有想象力的特性:它是基于annotation的,测试setup有了显著的提高,为新的runners扩展了hooks,而且甚至为assertEquals()增加了大多数人想要的数组比较.其他的框架如TestNG引入这些特性已经很旧了!你是对的,但是JUnit仍然是使用最广泛的(Java)测试框架,所以引入更多的新的技术成果是很有价值的.JUnit的优势在于每一个主要的开发工具都自动的支持它,不需要安装插件,不需要嵌入到我的基础代码中.并且由于它的开放的基于annotation的架构,它的扩展已经开发出来.仅凭这几点就足够了,赶快试试吧![/align][align=left]知道哪条路和实际走那条路是不同的![/align][align=left]-        Morpheus[/align][align=left]资源[/align] JUnit.org
JUnit 4 extensions
TestNG
[align=left]Ralf Stuckertcompeople AG的IT顾问,这是一家位于德国法兰克福的欧洲IT服务公司.[/align] 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: