JUnit重装上阵

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值