JUnit重装上阵

news/2024/7/6 1:10:12
                                                                        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注释来标注你的测试方法.看看下面的例子:
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() { ... }
}
它和使用 @Before and @After的规格一样.这意味着你可以有多个@BeforeClass 和 @AfterClass方法,同样,也可以从父类中继承得到.注意,这些方法必须是静态的.
测试 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 ")
    @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.
参数化的测试
除了 组合还有另外一个特别的runner:参数化.它使得你可以使用不同的数据组运行相同的测试.让我们来通过一个例子看看:我们将为计算给定数字n的 factorial的方法写一个测试:runner,JUnit 4
@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
资源
  • JUnit.org
  • JUnit 4 extensions
  • TestNG
Ralf Stuckert compeople AG IT 顾问 , 这是一家位于德国法兰克福的欧洲 IT 服务公司 .
 



http://www.niftyadmin.cn/n/3653776.html

相关文章

MySQL DBA 招聘小结

招聘MySQL DBA&#xff0c;与10来个2年MySQL DBA工作经验的朋友交流过&#xff0c;谈谈自己的心得&#xff1a;各位可以先看下互联网公司的薪资待遇&#xff1a; http://blog.csdn.net/mchdba/article/details/222743511 关于2年MySQL DBA经验看简历描述公司项目的时候&#…

使用Google Web Toolkit减轻AJAX的开发

使用Google Web Toolkit减轻AJAX的开发——使用GWT来创建一个简单的AJAX应用作者&#xff1a;Jeff Hanson&#xff0c;JavaWorld.com&#xff0c;12/13/06Google Web Toolkit(GWT)是一个Java开发框架&#xff0c;用来减轻AJAX(Asynchronous JavaScript and XML)的开发难度。使用…

oracle11g plsql调试存储过程卡死的处理技巧

>> PLSQL调试存储过程卡死有多次进行TEST调试存储过程&#xff0c;在某一个环境动不了&#xff0c;然后卡住&#xff0c;plsql界面进入假死状态了&#xff0c;而这个时候只有在windows里面的任务管理器里面强行关闭plsql的后台进程后&#xff0c;然后再次登录打开plsql&a…

Struts快速入门(五完)

用FormTag初始化ActionForm对象本节早前提到&#xff0c;HTML表单中动作URL被映射到一个配置&#xff0c;并轮流被映射到一个配置。FormTag中由action属性制定的URL被FormTag转换为一个在部署描述符中确定的路径结构URL。对于扩充的映射&#xff0c;这意味着资源扩展和指定的一…

Struts快速入门(三)

利用ActionMapping的命令模式Struts提供一个公开的基于XML语句的方法来说明请求URI中servlet路径与适当的请求处理器之间的映射。这个实现与命令模式[Gof]很相似。以下片断摘自struts-config.xml文件&#xff0c;下列声明用于建立ActionMapping配置对象&#xff0c;它是元素的运…

tomcat 应用Message file 'oracle net mesg Message' is missing问题

1&#xff0c;问题描述同事说crm登录hang住了&#xff0c;tomcat后台应用报错如下&#xff1a;2016-06-28 10:30:11,214 [com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#0] WARN [com.mchange.v2.resourcepool.BasicResourcePool] - com.mchange.v2.reso…

Struts快速入门(二)

Struts MVC 语义 我们从关键的Struts抽象概念开始&#xff0c;这是它MVC架构的核心。Struts使用Service to Worker模式实现了MVC模式。[Core]控制器对象控制器被ActionServlet类所实现。它提供一个中心位置来处理全部的终端请求。这就为处理视图和导航管理的控制层提供了更为清…

mongodb 最新版本高可用解决方案-replica sets副本集部署详细过程

Mongodb副本集&#xff1a;NoSQL的产生就是为了解决大数据量、高扩展性、高性能、灵活数据模型、高可用性。但是光通过主从模式的架构远远达不到上面几点&#xff0c;由此MongoDB设计了副本集和分片的功能&#xff0c;先来用用副本集。Mongodb副本集的同步机制&#xff1a;数据…