筑基JUnit

首先是一个提供了基本四则运算的类Calculator.java

package com.jadyer.junit4; //package com.jadyer.junit3; /** * 数学计算 */ public class Calculator { public int add(int a, int b) { return a + b; } public int minus(int a, int b) { return a - b; } public int multiply(int a, int b) { return a * b; } public int divide(int a, int b) throws Exception { if (0 == b) { throw new Exception("除数不能为零!"); } return a / b; } // The private method for JUnit4.x test private int addPrivate(int a, int b){ return a + b; } }

下面演示的是使用JUnit4.x编写的单元测试类CalculatorTest.java

package com.jadyer.junit4; import static org.junit.Assert.assertEquals; //静态导入 import static org.junit.Assert.fail; import java.lang.reflect.Method; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import com.jadyer.junit4.Calculator; /** * 在JUnit4中,不需要继承JUnit框架提供的相关类,它的测试方法须满足如下原则 * 1..public * 2..void * 3..无参 * 4..方法名称不必test开头,可随意命名。但为了增强可读性,实际开发中方法名也可能以test开头 * 5..使用@Test注解指明该方法为测试方法 */ public class CalculatorTest { private Calculator cal; //JUnit4中使用@Before标识的方法的作用,与JUnit3的setUp()是完全相同的 //而且JUnit4中用于初始化的方法的名字可随意,只需要提供@Before注解标识即可 //同理使用@After注解标识的方法的作用,就完全等价于JUnit3中tearDown()方法 @Before public void init(){ cal = new Calculator(); System.out.println("---init invoked---"); } @After public void destroy(){ System.out.println("---destroy invoked---"); } //@BeforeClass标注的方法,会在整个类加载前被执行一次,因此它是执行最早的,比@Before方法执行的时机还要早 //比如连接数据库,通常在初始部分连接一次数据库即可,不需要执行每个测试方法时都连接一次数据库,此时可使用@BeforeClass //但使用@BeforeClass注解的方法,要求是public static void的,而方法名随意 //由于该方法是在类被加载时执行一次,若不定义成static,那么执行该方法时就必须生成类的实例 //但是该类刚被JVM加载时,还没有生成对象。而一个方法被类调用,那么该方法就必然是static的 //所以这里的方法名中的static不是随便写的,而是根据Java的语法规则来定义的 //同样使用@AfterClass注解标识的方法的作用,与@BeforeClass作用完全相反 @BeforeClass public static void globalInit(){ System.out.println("---globalInit invoked---"); } @AfterClass public static void globalDestroy(){ System.out.println("---globalDestroy invoked---"); } @Test public void myAdd(){ int result = cal.add(1, 2); //Assert.assertEquals(3, result); assertEquals(3, result); } @Test public void myMinus(){ int result = cal.minus(1, 2); assertEquals(-1, result); } @Test public void myMultiply(){ int result = cal.multiply(2, 3); assertEquals(6, result); } @Test public void myDivide(){ try{ int result = cal.divide(6, 5); assertEquals(1, result); }catch(Exception ex){ ex.printStackTrace(); } } /** * 通过expected指定-->期望该测试方法所要抛出的异常,若该方法未抛出expected指定异常,认为测试失败;反之认为测试成功 * 但使用expected后,就不应该在测试方法中使用try{}catch()了,因为try{}catch()会捕获异常并处理,而不是把异常抛出 * 但我们可以在测试方法上使用throws Exception。这是JUnit4中测试异常的方式,远比JUnit3.8中测试异常的方式简单的多 */ @Test(expected = Exception.class) public void myDivide22()throws Exception{ cal.divide(1, 0); } /** * 使用timeout指定-->期望当前测试方法的执行时间,单位是毫秒 * 下面的timeout=100即指定该测试方法的执行时间不能超过100毫秒,否则视为测试失败 * 这里timeout实际上是一个小的Feature,即小的特征,它希望做一些很简单的性能测试 * 实际上真正做性能测试时,不会用这么简单的方式来做,它只是给我们提供了一些小的参考 */ @Test(timeout = 100) public void myDivide33(){ try { cal.divide(4, 2); //Thread.sleep(110); //让当前线程沉睡110毫秒 } catch (Exception e) { e.printStackTrace(); } } /** * 使用@Ignore标注的测试方法,在测试时将会被忽略掉,而不被执行 * 我们也可以使用@Ignore("This is ...")来描述一些提示信息 */ @Ignore @Test(expected = Exception.class) public void myDivide44()throws Exception{ cal.divide(1, 0); } /** * 私有方法的测试 * @see -------------------------------------------------------- * @see 此时需要在Calculator.java中添加一个私有方法,如下所示 * @see private int addPrivate(int a, int b){return a+b;} * @see -------------------------------------------------------- * @see 在测试私有方法时,绝对不要把源私有方法中的private改为public * @see 也就是说,绝对不要为了测试而修改源代码,除非通过测试发现了BUG * @see 但我们可以通过反射的方式来测试私有方法。反射可以突破私有的限制 * @see 在反射面前,公有的、私有的、受保护的、包级别的,一律都视而不见 * @see -------------------------------------------------------- */ @Test public void myAddPrivate(){ try{ Class<Calculator> clazz = Calculator.class; //Integer.TYPE == int.class -->获取int对应的class对象 //Integer.class -------------->获得Integer对应的class对象 //尽管Java中可以自动装箱和拆箱,但int和Integer使用的class对象是不一样的 //因此这里可以使用int.class或者Integer.TYPE,但是不可以使用Integer.class Method method = clazz.getDeclaredMethod("addPrivate", new Class[]{int.class, Integer.TYPE}); //将其值设为true之后,就可以访问任何级别的方法了 method.setAccessible(true); Object result = method.invoke(cal, new Object[]{1,2}); assertEquals(3, result); }catch(Exception ex){ ex.printStackTrace(); fail(); } } }

下面演示的是使用JUnit3.8编写的单元测试类CalculatorTest.java

package com.jadyer.junit3; import com.jadyer.junit3.Calculator; import junit.framework.Assert; import junit.framework.TestCase; /** * 在JUnit3.8中,测试类必须要继承TestCase父类 * @see ----------------------------------------------------------------------------------- * @see 单元测试不是证明您是对的,而是证明您没有错误 * @see ----------------------------------------------------------------------------------- * @see keep the bar green to keep the code clean * @see 即JUnit视图中的bar为绿色,表示测试通过。若bar为红色,表示测试有错误 * @see ----------------------------------------------------------------------------------- * @see DRY原则:Dont't Repeat Yourself * @see DRY原则:即--不要重复相同的代码 * @see DRY原则:但是在测试方法和测试方法之间,有时可能肯定会出现重复的代码 * @see DRY原则:对於单元测试,有这样的一个原则:测试跟测试之间互不干扰,互不影响 * @see DRY原则:即测试方法之间彼此是相互独立的 * @see ----------------------------------------------------------------------------------- * @see 源代码的改写跟测试代码的改写是相辅相成的 * @see 通过测试,发现问题,然后修改源代码。而源代码的修改,可能导致测试代码也要修改。往复循环。 * @see 这样做的话,表面上看,工作量是增加了,但实际上会保证项目的质量,而且并不会花费太多的时间 * @see 反而会降低时间,因为假设不做单元测试,若项目后期发现问题,那时修改BUG的时间会成倍的增加 * @see 另外编写测试代码时,也应该小心一些。因为测试代码也是代码,同样也会出现问题 * @see 但是我们不可以针对测试代码,再去写一个测试代码,否则就会是无穷无尽的死循环 * @see ----------------------------------------------------------------------------------- * @see 是否可能在执行所有的测试方法之前先执行一次初始化的setUp()方法,并且仅仅执行一次 * @see 然后再执行测试方法。当测试方法执行完毕,再执行资源销毁的tearDown(),同样仅仅执行一次 * @see 这种方式在JUnit3.8中是不存在的,而且这也是它的一个弊端 * @see 但是自从JUnit4.0出现之后,就为我们提供了这样的一种方式 * @see ----------------------------------------------------------------------------------- * @see 在JUnit3.8中,测试方法须满足如下原则 * @see 1..public * @see 2..void * @see 3..无参 * @see 4..方法名以test开头,即testXxx() * @see ----------------------------------------------------------------------------------- */ public class CalculatorTest extends TestCase { private Calculator cal; /** * 在执行每一个testXxx()之前,都会先执行setUp()方法,就是说有几个testXxx()方法,便执行几次setUp()方法 * 由于每执行testXxx()前都会运行一次setUp(),所以每一次testXxx()中的cal指向的都是不同的Calculator对象 */ public void setUp(){ cal = new Calculator(); } /** * setUp()用来完成初始化,tearDown()用来完成资源的回收 * 在执行完每一个testXxx()之后,都会去执行一次tearDown()方法 */ public void tearDown(){ //System.out.println("------tearDown is invoked------"); } public void testAdd() { int result = cal.add(1, 2); Assert.assertEquals(3, result); //断言:assert---这是JUnit中的验证,即断定我们的期望值与结果值是否一致 } public void testMinus() { int result = cal.minus(1, 2); Assert.assertEquals(-1, result); } public void testMultiply() { int result = cal.multiply(2, 3); Assert.assertEquals(6, result); } /** * 这里不能public void testDivide()throws Exception{},尽管符合语法规则,但不符合JUnit测试规则 * 因为它本来就是要测试我们的代码是否有问题,如果在这里也把异常抛出的话,那么这里的测试也就没有任何意义了 */ public void testDivide() { int result = 0; try { result = cal.divide(6, 4); } catch (Exception e) { e.printStackTrace(); //在控制台上就会打印出异常堆栈信息 //我们期望cal.divide(6, 4)是正常执行的 //不过一旦执行流程进入到catch{}里面的话,说明期望的跟实际的是不一致的,表明测试失败了 //因此为了完善我们的测试,就在这里加上了Assert.fail()代码,即立刻断言,让testXxx()失败 //这是JUnit进行单元测试的一些小的技巧,也是在实际工作当中用得比较多的一些经验的积累 Assert.fail(); } Assert.assertEquals(1, result); } /** * 边界值是BUG高发的一个地方,所以我们应该对边界的地方,多多的进行测试 */ public void testDivide22(){ Throwable tx = null; //定义一个Error和Exception的共同父类Throwable try { //我们期望的是该行代码抛出异常。根据对异常的理解,此时下一行的代码就不会再执行了 cal.divide(4,0); //表示断言失败。也就是说JUnit执行中遇到Assert.fail()时就会立刻停止执行当前的testXxx(),并且立刻失败 //由于我们期望cal.divide(4,0)会发生异常,所以这里的Assert.fail()就永远不会执行 //不过一旦Assert.fail()执行了,则说明我们期望的跟实际的情况是不吻合的,也就是说我们的测试是失败的 Assert.fail(); } catch (Exception ex) { ex.printStackTrace(); //在控制台上就会打印出异常堆栈信息 tx = ex; //如果发生异常,则将捕获的异常赋给tx } //断言tx是否不为空 Assert.assertNotNull(tx); //断言tx对象是否为Exception类型的 //这个getClass()方法返回的Class对象,就代表当前对象所对应的Class对象 //在实际开发中,这里的Exception.class一般都是自定义的异常 Assert.assertEquals(Exception.class, tx.getClass()); //在Exception类的父类Throwable中有一个getMessage()方法 //getMessage()返回的就是在构造Exception(String message)对象时,所传递的字符串对象 Assert.assertEquals("除数不能为零", tx.getMessage()); } }

最后:再附上JUnit3.8自身提供的执行单元测试类的TestRunner.class的使用方式

package com.jadyer.junit3; /** * @see --------------------------------------------------------------------------------- * 在junit.awtui和junit.swingui和junit.textui三个包中都提供了TestRunner.class类 * 这是JUnit本身提供的三种运行方式,不依赖于任何IDE,它可以通过命令行或者图形界面来独立运行 * 当JUnit和Ant结合时,它还是使用命令行的方式运行的,然后把结果输出给Ant,最后产生测试报告 * @see --------------------------------------------------------------------------------- * @see 直接Run As Java Application即可,此时控制台输出情况如下 * @see ..... * @see Time: 0.016 * @see OK (5 tests) * @see 第一行的5个圆点表示5个测试方法,一个圆点就表示一个测试方法通过了 * @see 第二行的Time指的就是总共经历的时间 * @see --------------------------------------------------------------------------------- */ public class TestRunnerDemo { public static void main(String[] args) { //以命令行的方式运行 junit.textui.TestRunner.run(CalculatorTest.class); //以AWT方式运行 junit.awtui.TestRunner.run(CalculatorTest.class); //以Swing方式运行 junit.swingui.TestRunner.run(CalculatorTest.class); } }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值