JUnit单元测试

Junit入门知识
 

JUnit的核心类主要有三个:TestCaseTestSuiteTestRunner
当你编写一个test case时,你就会创建一个TestCase对象;当你需要一次执行多个TestCase对象时,你可以创建TestSuite对象;为了执行TestSuite,你需要使用TestRunner对象。这三个核心类构成三重唱,共同完成测试结果。

TestCase(测试用例)--扩展了JUnitTestCase类的类。它以testXXX方法的形式包含一个或多个测试,一个test case把具有公共行为的测试归入一组。

TestSuite(测试集合)--一组测试。一个test case是把多个相关测试归入一组的便捷方式。若是你没有为TestCase定义一个test suite,那么JUnit会自动为你提供一个test suite,包含TestCase中所有的测试

TestRunner(测试运行器)--执行test suite的程序。JUnit没有TestRunner接口,但是有一个所有test runner都必须继承的BaseTestRunner

Assert(断言)--当条件成立时,Assert方法保持沉默,否则就抛出异常。

TestResult(测试结果)--包含了测试中发生的所有错误或失败。

Test接口--可以运行Test并把结果传递给TestResult

TestListener接口--测试中若产生事件(开始、结束、错误、失败)会通知TestListener

TestRunner主要使用方式有两种:ConsoleSwingUI,另外有一种AWT的已经不常用了,而且你还可以通过继承BaseTestRunner来定义你自己的TestRunner

TestSuite相当于一个容器,可以把几个测试作为一个集合放在一起运行,TestRunner只负责启动TestSuite,而具体执行哪些TestCase是由TestSuite来决定。TestSuite会为每个TestCase需要测试的方法生成一个实例,保持了各个单元测试的独立性。

一个很有趣的现象,TestCaseTestSuite都实现了Test接口,而TestSuiteaddTest方法接收的参数类型却是Test接口类型,而不是TestCase类型,这就意味着可以给TestSuite增加TestCase,也可以增加TestSuite,这种灵活性很方便你创建各种Suite或者组合出TestAll类。

TestResult负责收集TestCase的执行结果,TestRunner使用TestResult来报告测试结果,若是TestResult集合中没有TestFailure对象,那么所有的测试就是正确的。

除了TestRunner可以报告测试结果之外,TestListener也可以帮助对象访问TestResult并创建有用的报告,可以有任意数量的TestListenerJUnit框架注册。

TestCase包含两个主要部件:Fixture和单元测试

Fixture--运行一个或多个测试所需的公用资源或数据集合。比如象数据库连接等。TestCase会通过setUPtearDown方法来自动创建和销毁Fixture,它会在每个测试之前调用setUp,在每个测试之后调用tearDown。把不止一个测试方法放进TestCase的一个重要理由就是可以共享Fixture代码。

单元测试模块主要是包含了Assert类,它提供了一系列assertXXX的方法,很方便我们进行重复的测试,另外TestCase还提供了一些方法。

 

 

 

 

 

 

 

 

------------------------------------------------------------------------------------------------------------------------------------------

 

JAVA JUNIT中有关TestSuite的使用

 自己定义的TestCase,并使用TestRunner来运行测试,事实上TestRunner并不直接运行 TestCase上的单元方法,而是透过TestSuite,TestSuite可以将数个TestCase在一起,而让每个TestCase保持简单。


来看看一个例子:
 
MathToolTest.java
view plain
  1. package onlyfun.caterpillar.test;  
  2.   
  3. import onlyfun.caterpillar.MathTool;  
  4. import junit.framework.TestCase;  
  5.   
  6. public class MathToolTest extends TestCase {  
  7.     public MathToolTest(String testMethod) {  
  8.         super(testMethod);  
  9.     }  
  10.   
  11.     public void testGcd() {  
  12.         assertEquals(5, MathTool.gcd(105));  
  13.     }  
  14.   
  15.     public static void main(String[] args) {  
  16.         junit.textui.TestRunner.run(MathToolTest.class);  
  17.     }  
  18. }  

在这个例子中,您并没有看到任何的TestSuite,事实上,如果您没有提供任何的TestSuite,TestRunner会自己建立一个,然後这个 TestSuite会使用反射(reflection)自动找出testXXX()方法。

如果您要自行生成TestSuite,则在继承TestCase之後,提供静态的(static)的suite()方法,例如:
public static Test suite() {
     return new TestSuite(MathTool.class);
}

如果您没有提供任何的TestSuite,则TestRunner就会像上面这样自动为您建立一个,并找出testXXX()方法,您也可以如下面定义 suite()方法:
public static Test suite() {
     TestSuite suite = new TestSuite(MathTool.class);
     suite.addTest(new MathToolTest("testGcd"));
     return suite;
}
 
JUnit并没有规定您一定要使用testXXX()这样的方式来命名您的测试方法,如果您要提供自己的方法(当然JUnit 鼓励您使用testXXX()这样的方法名称),则可以如上撰写,为了要能够使用建构函式提供测试方法名称,您的TestCase必须提供如下的建构函 式:
public MathToolTest(String testMethod) {
    super(testMethod);
}
 
如果要加入更多的测试方法,使用addTest()就可以了,suite()方法传回一个TestSuite物件,它与 TestCase都实作了Test介面,TestRunner会调用TestSuite上的run()方法,然後TestSuite会将之委托给 TestCase上的run()方法,并执行每一个testXXX()方法。

除了组合TestCase之外,您还可以将数个TestSuite组合在一起,例如:
public static Test suite() {
    TestSuite suite= new TestSuite();
    suite.addTestSuite(TestCase1.class);
    suite.addTestSuite(TestCase2.class);
    return suite;
}
 
如此之来,您可以一次运行所有的测试,而不必个别的运行每一个测试案例,您可以写一个运行全部测试的主测试,而在使用TestRunner时呼叫 suite()方法,例如:
junit.textui.TestRunner.run(TestAll.suite());

TestCase与TestSuite都实作了Test介面,其运行方式为 Command 模式 的一个实例,而TestSuite可以组合数个TestSuite或TestCase,这是 Composite 模式 的一个实例。

 

 

 

 

 

 

 

 

 

------------------------------------------------------------------------------------------------------------------------------------------

 了解 JUnit 核心类、接口及生命周期

 

简介: Junit 从问世至今已有 12 年的历史,期间功能不断完善,用户逐渐扩大,已经成为 Java 软件开发中应用最为广泛的测试框架。本文着重介绍 JUnit 的核心接口、核心类以及 TestCase 的生命周期,以便读者从架构层面掌握这个工具。

 

1997 年,Erich Gamma 和 Kent Beck 为 Java 语言创建了一个简单但有效的单元测试框架,称作 JUnit。JUnit 很快成为 Java 中开发单元测试的框架标准。世界上无数软件项目使用它。本文将介绍 JUnit 的核心接口,核心类以及 JUnit 的生命周期。

JUnit 核心接口及核心类

了解 JUnit 的生命周期之前,先了解 JUnit 的核心接口和类是有必要的,这对于了解 TestCase 的生命周期有很大的帮助。

  • Test:是 TestCase、TestSuite 的共同接口。run(TestResult result) 用来运行 Test,并且将结果保存到 TestResult。

  • TestCase:Test 的接口的抽象实现,是 Abstract 类,所以不能实例化,能被继承。其中一个构造函数 TestCase(String name),根据输入的参数,创建一个测试实例。参数为该类的以 test 开头的方法名,把它添加到 TestSuite 中,指定仅仅运行 TestCase 中的一个方法。

  • TestSuite:实现 Test 接口。可以组装一个或者多个 TestCase。待测试类中可能包括了对被测类的多个 TestCase,而 TestSuit 可以保存多个 TestCase,负责收集这些测试,这样就可以一个 Suite 就能运行对被测类的多个测试。

  • TestResult:保存 TestCase 运行中的事件。TestResult 有 List<TestFailure> fFailuresList<TestFailure> fErrors。fFailures 记录 Test 运行中的 AssertionFailedError,而 fErrors 则记录 Exception。Failure 是当期望值和断言不匹配的时候抛出的异常,而 Error 则是不曾预料到的异常,如:ArrayIndexOutOfBoundsException。

  • TestListener:是个接口,对事件监听,可供 TestRunner 类使用。

  • ResultPrinter:实现 TestListener 接口。在 TestCase 运行过程中,对所监听的对象的事件以一定格式及时输出,运行完后,对 TestResult 对象进行分析,输出的统计结果。

  • BaseTestRunner:所有 TestRunner 的超类。

  • java Junit.swingui.TestRunner:实现 BaseTestRunner,提供图形界面。从 4.0 版本起,就没有再提供这个类。这是 4.0 版本和之前版本的显著变化之一。

  • java Junit.textui.TestRunner:实现 BaseTestRunner,提供文本界面。下面将以它做为例子讲解 JUnit 生命周期。

TestCase 实例

了解了前面的几个类,下面将看一个例子:

public class TestShoppingCart extends TestCase { 
    double unitPrice = 5; 
    int quantity = 6; 
    double discount=0.2; 

    @Before 
    public void setUp() throws Exception { 
        System.out.println(" Up "); 
    } 

    @After 
    public void tearDown() throws Exception { 
        System.out.println(" Down "); 
    } 
	
    public void testPay() { 
        double total = unitPrice * quantity; 
        assertEquals(30, total); 
    } 
    
    public void testPayWithDiscount() { 
        double total = unitPrice * quantity*(1-discount); 
        assertEquals(24.0, total); 
    }
}

两种不同参数运行 TestCase

参数 1:

输入:

>java junit.textui.TestRunner  TestShoppingCart

输出:

Up 
testPay! 
Down 
Up 
testPayWithDiscount! 
Down 

参数 2:

输入:

> java junit.textui.TestRunner -m TestShoppingCart.testPayWithDiscount

输出:

Up 
testPayWithDiscount! 
Down

参数 1:TestCase 名字,该类的所有的以 test 开头的 public 方法都会执行。

参数 2:参数 -m,仅仅运行该类的该方法。

TestRunner 还提供了其他的参数 -wait:(最大响应时间),-v:查看 JUnit 版本号。从输出可以看出,参数一: testPay()testPayWithDiscount() 都运行;参数二:仅仅运行参数中的 testPayWithDiscount()。对比两个输出结果,setUp() 在每个方法运行前运行一次,teardown() 在每个方法运行后执行一次。后面将会详细介绍。

TestRunner 处理两种不同的参数

TestRunner main() 方法中,生成一个 TestRunner 实例,调用 start(args) 方法。在 start 方法中,JUnit 对输入参数进行处理,首先检查 -m、-v、-wait 等参数,对他们分别进行处理。如果有 -m 参数,将会根据“.”的位置,分割得到 className 和 methodName.

参数一:

首先调用 getTest(),通过 Java 反射实例化 TestSuite:

Class testClass = Class.forName(suiteClassName).asSubclass(TestCase.class); 

new TestSuite(testClass) 

TestSuite 构造函数中,通过调用 Class.getDeclaredMethods(),得到这个类的所有 Public 的方法,当然也包括构造函数,test 开头和非 test 开头的 public 方法。对所有方法进行过滤,仅仅保留 public 并且以“test”开头的方法,本例中为 testPay()testPayWithDiscount()。然后分别调用 TestSuite 的 createTest() 为每个方法生成一个实例:

theClass.getConstructor(String.class).newInstance(new Object[0]);
			

并且都保存在 Vector<Test> fTests 中。

参数二:

与方法一不同的的是,并不通过反射获得相应的方法,因为参数中指定了特定的方法。直接根据输入参数调用 TestSuite 的 createTest(),通过反射直接生成 TestCase 实例。

TestCase 实例的运行

生成 TestCase 实例后,两种参数都将调用 TestRunner 的 doRun() 方法。下面将对第二种参数进行详细介绍,介绍一个 TestCase 实例是怎么运行的,并且怎样与 TestResult 和 TestListener 结合。

doRun() 方法中,实例化 TestResult result, 为 result 加上 Listener (new ResultPrinter()),用来监听 Test 运行中的事件。然后运行 TestResult.Run(test)run() 方法中调用 TestCase 的 runBare()runBare() 会把所有的异常都抛出来,result 将接受到所有的异常。runBare() 首先会运行 setup(),接着运行 runTest(), 最后 tearDown()。回头再看前面的 output,就明白了为什么 setup()tearDown() 会在每个方法运行前和后运行,对于参数二,运行了两次。

TestResult

TestResult 有两个 List,用来记录 Exception 和 Failure。捕获 runBare() 抛出的 Exception,首先判断是否为 AssertionFailedError,是则调用 addFailure() 把,把异常加到 fFailures。否则则并调用 addError() 方法,把异常加到 fErrors 中。

catch (AssertionFailedError e) { 
    addFailure(test, e); 
} 
catch (ThreadDeath e) { // don't catch ThreadDeath by accident 
    throw e; 
} 
catch (Throwable e) { 
    ddError(test, e);
}

TestListener

前面提到 result 加上了一个 ResultPrinter,ResultPrinter 会记录运行中的所有 Exception,并且实时地以不同的格式输出。当所有的 Test 都运行完毕后,ResultPrinter 会对 result 进行分析,首先输出运行的时间,接着 printError() 输出 fErrors 的个数,printFailures() 则输出 fFailures 的个数。PrintFooter() 根据 result.wasSuccessful(),如果成功,则打印 OK 和 test 运行的总次数,如果失败,则打印出 test 总的运行的个数,失败和错误的个数。


参数一的统计输出结果:

				
Time: 0.016 
There was 1 failure: 
1) testPay(TestShoppingCart)junit.framework.AssertionFailedError: 
expected:<30> but FAILURES!!! 
Tests run: 2,  Failures: 1,  Errors: 0 


清单一:
				
synchronized void print(TestResult result, long runTime) { 
    printHeader(runTime); 
    printErrors(result); 
    printFailures(result); 
    printFooter(result); 
} 


清单二:
				
protected void printFooter(TestResult result) { 
    if (result.wasSuccessful()) { 
        getWriter().println(); 
        getWriter().print("OK"); 
        getWriter().println (" (" + result.runCount() + " test" 
            + (result.runCount() == 1 ? "": "s") + ")"); 

    } else { 
        getWriter().println(); 
        getWriter().println("FAILURES!!!"); 
        getWriter().println("Tests run: "+result.runCount()+ 
            ",  Failures: "+result.failureCount()+ 
            ",  Errors: "+result.errorCount()); 
    } 
    getWriter().println(); 
} 

完整生命周期

整个生命周期将在下图显示:


图 1. Junit 完整生命周期
JUnit单元测试_第1张图片

总结

通过上面的介绍,本文深入地讲解了 JUnit 的核心类和接口,TestCase 的完整生命周期。掌握了这些,开发者有了更加灵活的自用度,可以根据自己特定的项目的特性,定制最合适自身的 MyTestRunner,MyTestResult,MyTestSuite,MyTestListener。从而提高工作效率,发挥 JUnit 的最大作用。


参考资料

学习

  • 阅读 JUnit FAQ,了解他人学习 JUnit 所遇到的问题以及解决的办法。

  • 阅读 JUnit 总结,了解他人对 JUnit 的总结。

  • 浏览 JUnit.org:JUnit 的官方网站,可以得到最新版本的 JUnit,,学习 JUnit 的 Tutorials,JavaDoc 及详细信息。

  • 分析 JUnit 框架源代码”(developerWorks,2009 年 5 月):本文在展示代码流程 UML 图的基础上,详细分析 JUnit 的内部实现代码的功能与机制。

  • 探索 JUnit 4.4 新特性”(developerWorks,2008 年 9 月):本文通过理论分析和详细例子向读者阐述 JUnit 4.4 所带来的最新特性。

  • 深入探索 JUnit 4”(developerWorks,2007 年 3 月):该教程包括几个在 Eclipse 下运行的测试样例,以及如何在较早的 Ant 版本中运行 JUnit 4 的指导。

  • 单元测试利器 JUnit 4”(developerWorks,2007 年 2 月):本文主要介绍了如何使用 JUnit 4 提供的各种功能开展有效的单元测试,并通过一个实例演示了如何使用 Ant 执行自动化的单元测试。

  • JUnit 反模式”(developerWorks,2005 年 8 月):介绍一些常见的 JUnit 反模式,并说明如何解决它们。

  • developerWorks Java 技术专区:可以找到几百篇关于 Java 编程的各个方面的文章。

 

 

 

 

 

------------------------------------------------------------------------------------------------------------------------------------------

JUnit单元测试


主要内容
为什么要进行单元测试
单元测试概述
JUnit简介和经验总结


“测试不是我的工作”
测试是测试部门的责任,我的责任应该关注在写代码上;
测试不是一种技术工作,毫无乐趣可言,请不要骚扰我。我可是一个了不起的SSH程序员
我们有测试人员,有集成/系统/确认测试,他们迟早会发现我的错误,请不要浪费我的时间;
不要侮辱我,我写的程序,怎么可能有错误。测试是完全没必要的。


程序员的难题
开发的模块出现问题,很难定位,已经熬了几个通宵了!!!


后果
软件的质量完全取决于程序员的个人技能和责任心,具有很大的随机性
后期维护成本高昂
1个月的开发,几天的测试,然后花1,2年的时间去修补错误
这个项目我已经维护了3年了
根本原因是软件自身复杂的结构

 

现实中的发现
编码阶段引入的缺陷远远多于其它阶段
系统测试发现的缺陷大多数是编码缺陷
测试版本频繁,测试和项目进度被无休止的拖延
测试的时间和成本


单元测试
最高的成本收益比
减少联调和后续测试的时间
BUG更容易定位
更有信心去修改老代码
主要内容
为什么要进行单元测试


什么是单元测试(Unit Test)


单元测试测试的软件最小的可执行单元的正确性,即类或方法;
单元测试通常是一段可执行代码,并能验证执行结构是否和预期相等;
单元测试可以是黑盒也可以是白盒,取决于执行方法

单元测试是其他类型测试的基础。不认真,完整的单元测试会导致其他类型测试起不到好的效果
程序员最了解自己的程序单元,最适合做单元测试
传统的重量级的方法学里,UT test case由设计人员在系统设计阶段开发,并用来验证编码人员的工作质量

 

单元测试任务
单元接口测试
单元局部数据结构测试
单元中重要的执行路径测试
单元的各类错误处理路径测试
单元边界条件测试


单元测试原则
应该尽早地进行软件单元测试。
应该保证单元测试的可重复性。
尽可能地采用测试自动化的手段来支持单元测试活动。

 

单元测试一定要自动化
只有用代码编写的UT,才能够重现,才能真正节约未来手工测试的时间。
只有用代码编写的UT ,才能做到自动化,才能在软件开发的任何时候都能快速,简单的大批量执行,保证能准确地定位错误,保证不会因为修改而引入新的错误。在系统开发的后期尤为明显。
自动化的UT,才能保证回归测试的有效执行。


单元测试的必要性
带来更大的测试范围
带来团队合作的可能
防止衰退,减少调试
使得重构可行
改进实现设计
当做开发者文档来用
非常有趣


单元测试是成本最低的测试活动
单元测试节约的时间
编写UT代码的时间节约了未来修改/维护低质量代码的时间
学习做UT的时间,是为了以后你可以更好的关注你的代码
如果使用Test-driven的思,单元测试自身就变成设计的一部分,你不会再感到是在浪费时间,编写UT的过程,就是设计的过程
  UT快速的定位错误所在,节约了你调试的时间。


程序员的责任
程序员的价值在于和他人合作,开发出高质量的代码,而不是一堆新技术名词堆砌的虫件(bugware)。
程序员必须对自己的代码质量负责,单元测试是对自己代码质量的基本承诺。
程序=UT+CODE
不做单元测试,就会影响团队其他人员的工作。测试人员有权利对没有做过UT的代码说No.不愿意做UT的人,不属于任何团队。

单元测试工具和框架
目前的最流行的单元测试工具是xUnit系列框架,常用的根据语言不同分为JUnit(java),CppUnit(C++),DUnit (Delphi ),NUnit(.net),PhpUnit(Php )等等。
Junit测试框架的第一个和最杰出的应用就是由Erich Gamma (《设计模式》的作者)和Kent Beck(XP(Extreme Programming)的创始人 )提供的开放源代码的JUnit。 

 

JUnit简介和经验总结


Junit框架
JUnit  Test case
JUnit assertXXX( )
JUnit Set Up and Tear Down
JUnit Set Up and Tear Down --One time
JUnit Organizing Tests into Test Suites

 

第一种方法将自动执行testGame所有的testXXX方法

public class TestGame extends TestCase { ...
      public static Test suite( ) { return   new TestSuite(TestGame.class);
      }
 }

 

第二种方法只执行指定的方法
public static Test suite( ) { TestSuite suite = new TestSuite( ); suite.addTest(new TestGame("testCreateFighter"));
      suite.addTest(new TestGame("testSameFighters")); return suite;
}

 

JUnit Exception Handling
JUnit Repeating Tests
JUnit Running Tests Concurrently


JUnit 测试的命名规范
TestSuite处理测试用例有6个规约
测试用例必须是公有类(Public)
 测试用例必须继承与TestCase类  
 测试用例的测试方法必须是公有的( Public )
 测试用例的测试方法必须被声明为Void
 测试用例中测试方法的前置名词必须是test
 测试用例中测试方法无任何传递参数


JUnit经验总结
不要用TestCase的构造函数初始化,而要用setUp()和tearDown()方法。
不要依赖或假定测试运行的顺序,因为JUnit利用Vector保存测试方法。所以不同的平台会按不同的顺序从Vector中取出测试方法。
避免编写有副作用的TestCase。例如:如果随后的测试依赖于某些特定的交易数据,就不要提交交易数据。简单的回滚就可以了。
当继承一个测试类时,记得调用父类的setUp()和tearDown()方法。

将测试代码和工作代码放在一起,一边同步编译和更新。
测试类和测试方法应该有一致的命名方案。如在工作类名前加上test从而形成测试类名。
确保测试与时间无关,不要依赖使用过期的数据进行测试。导致在随后的维护过程中很难重现测试。
编写测试时要考虑国际化的因素。不要仅用母语的Locale进行测试。
尽可能地利用JUnit提供地assert/fail方法以及异常处理的方法,可以使代码更为简洁。
测试要尽可能地小,执行速度快。


单元测试经验


测试驱动开发
编写单元测试用例促进解除模块之间的耦合。先编写测试用例,强迫自己从利于调用者的角度来设计单元,关注单元的接口。为了便于调用和独立测试,必须降低单元和周边环境的耦合程度,单元的可测试性得到加强,模块化程度得到提高。这样单元的可重用性也容易被考虑和提高。

重构
测试用例数量是逐步增加的,软件功能也在此过程中得到增强、更新和优化。当新的需求变化到来时,测试用例被增加或修改,难以适应测试用例的软件单元被重构。经常发生变化的测试用例和软件模块被分离出来,进行重构和优化,使它们更加容易应付需求的变化

 

你可能感兴趣的:(JUnit单元测试)