当前位置: 首页 > 图灵资讯 > 技术篇> 用JUnit框架实现Java单元测试

用JUnit框架实现Java单元测试

来源:图灵教育
时间:2023-06-30 16:35:25

http://tech.it168.com/j/2007-08-27/200708271737659.shtml

单元测试是整个测试过程中最基本的部分,要求程序员尽早发现问题并控制问题。此外,如果集成测试有问题,它们可以帮助诊断。这为在软件开发过程中建立高效的事件反应机制奠定了坚实的基础。  Junit为Java程序开发者实现单元测试提供了一个框架,使Java单元测试更加标准化和有效,更有利于测试的集成。  Junit的内部结构  Junit的软件结构  JUnit 有七个包,核心包是junitt.framework 和junit.runner。Framework包负责整个测试对象的结构,Runner负责测试驱动。 Junit的类结构  Junit有四个重要类别:TestSuite、TestCase、TestResult、TestRunner。前三类属于Framework包,后一类在不同的环境下是不同的。这里使用文本测试环境,所以使用 junit.textui.TestRunner。各类职责如下:   1.TestResult,负责收集Testcase执行的结果,将结果分为客户可预测的Failure和未预测的Eror两类。同时负责将测试结果转发给TestListener(该接口由TestRunner继承);   2.TestRunner,负责跟踪整个测试过程的客户对象调用起点。可显示返回的测试结果,并报告测试进度。   3.TestSuite, 负责所有Testcase的包装和操作。   4.TestCase, 客户测试类要继承的类,负责对客户类进行初始化,并调用测试方法。  还有两个重要的接口:Test和TestListener。   1.Test, 包括两种方法:run() 和countestcases(),它是对测试动作特征的提取。   2.TestListener, 包括四种方法:addError()、addFailure()、startTest()和endtest()是提取测试结果的处理和测试驱动过程的动作特征。  以下两种类图(篇幅有限,只显示主要部分)很好地阐明了类之间的关系,以及junit的设计目标(如图1所示)。Composite模式用于测试案例类别。这样,客户的测试对象就变成了“部分-整体”的层次结构。客户代码只需要继承Testcase,就可以轻松地与其他现有对象结合使用,使单元测试的集成更加方便。

用JUnit框架实现Java单元测试_测试方法

图1 测试结构图

图2是测试跟踪类图。图2左侧TestSuite包含测试对象集,右侧包含测试结果集。如何处理结果,包含哪些测试对象,并没有立即得出结论,而是尽量延迟到具体实现。例如,实现接口Testlistener的Junit包含:junit.awtui.TestRunner、junit.swingui. TestRunner、junit.ui.testrunner等,甚至客户用自己的类别实现testlistener,从而达到多样化的目的。

用JUnit框架实现Java单元测试_测试用例_02

图2 测试跟踪图

从以上两个类图中,我们可以了解Junit对单元测试的基本思路,这个框架的核心是结果集和案例集。

Junit的实现过程  使用Junit的典型方法是继承Testcase类别,然后重载它的一些重要方法:setUp()、teardown()、runTest()(这些都是可选的),最后将这些客户对象组装成TestSuite对象,交由 junit.textui.TestRunner.run (案例集) 驱动。下面分析案例集是如何运作的。以下分析案例集是如何运作的。  图3基本阐述了JUnit的测试过程架构。本图将从不同的角度进行详细分析。

用JUnit框架实现Java单元测试_Test_03

图3 测试序列图

首先,分析对象的创建。客户类负责创建Suite和atestruner。请注意,类Testruner含有静态函数runn(Test),它创建自己,然后调用dorun()。该函数通常被客户调用,代码如下:

static                 public                 void         run(Test suite){   TestRunner aTestRunner        =                 new         TestRunner();        //        新建测试驱动                   aTestRunner.doRun(suite,         false        );        //        测试驱动运行测试集                }

Suite对象负责创建许多测试案例,并将其包容到自己身上。客户测试案例继承Testcase,它将类别传递给Suite对象,而不是对象。Suite对象负责分析这些类别,提取结构函数和待测试方法。以待测试方法为单位构建测试案例的fname是待测试方法的名称。ATestrunner创建了测试结果集。这似乎与之前描述的类图有些矛盾。在那里,一个测试集可以包含许多不同的测试驱动器。似乎首先创建结果集是理想的。显然,这里只使用一种方法来处理测试结果,所以这样做也是可行的。  其次,从测试动作的实施来分析,测试真的是从suite上进行的.run(result) 开始的。其代码如下:

public                 void         run(TestResult result){          //        所有测试案例都是从案例集中获得的,分别执行                           for         (Enumeration e        =         tests(); e.hasMoreElements(); )   {           if         (result.shouldStop() )           break        ;   Test test        =         (Test)e.nextElement();   runTest(test, result);   }}

一旦测试案例开始实施,首先使用回调策略将自己交给Result。测试这样做的每一步,测试驱动程序atest Runner可以跟踪处理。这实际上建立了一个巨大的监控系统,可以随时对发生的事件给予不同程度的关注。  本文对涉及的动作行为的设计模式进行了分析:  1. Template Method (模板法)类行为模式,其本质是首先建立方法的骨架,并尽可能地将方法的具体实现推回。TestCase.runBare()采用这种模式,客户可以重载三种方法,从而提高测试的可伸缩性。

public                 void         runBare() throws Throwable{   setUp();           try         {runTest();}           finally         {tearDown();}}

ExceptionJunit的抛出机制  Junit的异常层次分为三层:1.Failure,Assert方法可以检测到客户预测的测试失败;2. Error,事故造成的客户测试;3.Systemerror, Junit线程死亡级异常,这种情况通常很少发生。在Testresult类RunProtected()中,Junit的这三种异常得到了很好的体现。Test的执行方法用Protectable接口包装在这里。事实上,protectable接口.testttect执行是test.runBare()。

public                 void         runProtected(final Test test, Protectable p){           try         {p.protect();}           catch         (AssertionFailedError e)    {addFailure(test, e);}           catch         (ThreadDeath e)   {rethrow e;}           catch         (Throwable e)   {addError(test, e);}}

首先检查代码是否为Assertion FailedError,然后判断Threaddeath是否严重。为了保证线程的真实死亡,这种异常必须是Rethrow。如果没有,说明是意外。  前两种异常保存在测试结果中,直到整个测试完成,并依次打印供客户参考。 实施JUnit的几点建议  从以上分析中,我们可以了解Junit的结构和流程,但在实际应用Junit时,有几点建议需要说明,如下:  1. 客户可以重载runtest(),其缺点是调用方法称为fname的测试方法。如果客户不使用TestSuite加载TestCase,他们尤其需要重新加载。当然,这种方法不同意使用,不利于集成。另外,setUp()和teardown()的功能似乎与结构函数相似,但如果测试案例之间存在类继承关系,使用结构函数初始化一些参数会导致数据混乱,不利于判断测试结果的有效性。  2. 待测函数的调用顺序不确定,所采用的数据结构为Vector()。若需要顺序关系,可将其组合在一起,然后采用相同的测试方法。  3. 为了使测试结果清晰,程序中最好不要有打印输出,或者程序的打印输出和Junit测试的打印输出不要使用相同的数据源System.out。其实这是两种测试习惯,直接打印输出比较传统,从测试动机上考虑也比较随意,结果需要人工观察。若直接打印输出较多,观察者可能无法获得令人满意的结果。   另外,如何扩展这个测试框架? junit.extensions包给出了一些提示。我们可以使用junitt.extensions. Activetest在不同的线程中运行测试实例。 Decorator模式可用于为测试案例添加新功能,请参考junitor.extensions.Testdecorator及其子类junitor.extensions.TestSetup、junit.extensions.RepeatedTest。这些只提供了一些拓宽思路,涉及到具体的测试目标,需要进一步挖掘。

===========

http://tech.it168.com/j/2007-08-29/200708291342456.shtml

1、相关概念 Ø JUnit:Java测试框架开发源代码,用于编写和操作可重复测试。它是单元测试框架系统xunit(java语言)的一个实例。主要用于白盒试验和回归试验。 Ø 白盒测试:将测试对象视为一个打开的盒子,程序内部的逻辑结构和其它信息对测试人员进行测试 工作人员是公开的。 Ø 回归测试:自动测试工具对软件或环境修复或更正后的重新测试特别有用。 Ø 单元测试:测试一个功能或代码块,以测试最小粒度。一般由程序员来做,因为它需要知道内部程序设计和编码的细节。2、 单元测试 2.1、单元测试的好处 Ø 提高开发速度,以自动化的方式进行测试,提高测试代码的执行效率。 Ø 为了提高软件代码的质量,它使用小版本发布到集成,方便人员消除错误。同时,引入重构概念,使代码更加清洁和灵活。 Ø 提高系统的可靠性,这是一种回归测试。在修复或更正后支持“重新测试”,以确保代码的正确性。 2.2、单元测试的目标对象 Ø 面向过程的软件开发针对过程。 Ø 针对对象的软件开发。 Ø 可进行类别测试、功能测试、接口测试(最常用于测试类别的方法)。 2.3、单元测试工具和框架 目前最流行的单元测试工具是XUnit系列框架,根据不同的语言,常用的单元测试工具分为JUnit。(java),CppUnit(C++),DUnit (Delphi ),NUnit(.net),PhpUnit(Php )等等。 单元测试框架的第一个和最突出的应用是Erich Gamma (设计模式的作者)和Kent Beck(XP(Extreme Programming)的创始人 )JUnit提供开放源代码。 3、Junit介绍 3.1、Junit和Junit测试编写原则的好处 使用JUnit的好处: Ø 测试代码可以与产品代码分开。 Ø 对于某一类的测试代码,可以通过较少的变化应用于另一类的测试。 Ø Junit与Ant的结合可以实现增量开发,易于集成到测试人员的构建过程中。 Ø Junit是一个可以进行二次开发的公开源代码。 Ø JUnit可以很容易地扩展。 Junit测试编写原则: Ø 简化测试的编写,包括测试框架的学习和实际测试单元的编写。 Ø 保持测试单元的持久性。 Ø 相关测试可以用现有的测试来编写。 3.2、Junit的特点 Ø 用断言法判断期望值与实际值的差异,并返回Boolean值。 Ø 使用共同的初始变量或实例来测试驱动设备。 Ø 测试包结构易于组织和集成运行。 Ø 支持图形交互模式和文本交互模式。 3.3 由JUnit框架组成 Ø 测试目标的方法和过程集合可称为测试用例(TestCase)。 Ø 测试用例的集合可容纳多个测试用例(TestCase),称之为测试包(TestSuite)。 Ø 描述和记录试验结果。(TestResult) 。 Ø 事件监听者在测试过程中(TestListener)。 Ø 描述每种测试方法与预期不一致的情况,称为测试失败元素(TestFailure) Ø JUnit Framework中的错误异常(AssertionFailedError)。 Junit框架是典型的Composite模式:TestSuite可以容纳从Test衍生出来的任何对象;当调用TestSuite对象的run()方法时,它会遍历自己容纳的对象,并逐个调用它们的run()方法。3.5 Junit中常用的界面和类别 Ø Test接口:运行测试和收集测试结果 Test接口采用Composite设计模式,单独测试用例(TestCase),聚合试验模式(TestSuite)及测试扩展(TestDecorator)共同接口。 它的public int countTestCases()方法,用来统计测试时有多少个TestCase。另外一种方法是public void run( TestResult ),Testresult是实例测试结果, 本次测试采用run方法进行。 Ø Testcase抽象类:定义测试中的固定方法 Testcase是Test接口的抽象实现,其构造函数Testcase(不能实例化,只能继承)(string name)根据输入的测试名name创建测试实例。因为每个Testcase在创建时都必须有一个名字,如果测试失败,就可以识别出哪个测试失败。 setup()包含在Testcase类别中、tearDown()方法。 setUp()方法集中初始化测试所需的所有变量和实例,并在依次调用测试类中的每个测试方法之前再次执行setup()方法。 tearDown()方法是在每个测试方法之后,释放测试程序方法中引用的变量和实例。 当开发人员编写测试用例时,他们只需要继承Testcase来完成run方法,然后Junit获得测试用例,执行其run方法,并在testresult中记录测试结果。 Ø Assert静态类:一系列断言方法的集合 Assert包含一组静态测试方法,用于期望值与实际值之间的比较是否正确,即测试失败,Assert类会抛出AssertionFailedEror异常,Junit测试框架将这个错误归类为Failes并记录下来,并标记为未通过测试。如果该方法中指定了String类型的传参,则该参数将被用作AssertionFailedError异常的识别信息,并告诉测试人员异常变化的详细信息。 JUnit 提供了基础断言、数字断言、字符断言、布尔断言、对象断言六大类31组断言。assertequals(Object expcted,Object actual)equals()方法用于内部逻辑判断,说明在判断两个例子的内部哈希值是否相等时,最好用这种方法来比较相应例子的值。 asssertsame(Object expected,Object actual)Java运算符“==”用于内部逻辑判断,这表明判断两个例子是否来自同一个引用(Reference),最好用这种方法比较不同类型实例的值。 asserEquals(String message,String expected,String actual)该方法逻辑地比较了两个字符串,如果不匹配,则显示了两个字符串之间的差异。 ComparisonFailure类提供两个字符串的比较,如果不匹配,则给出详细的差异字符。 Ø Suite测试包Test??多个测试的组合 Testsuite负责组装多个Testte Cases。待测类可能包括被测类的多个测试,TestSuit负责收集这些测试,使我们能够在一个测试中完成被测类的所有多个测试。Testsuite类实现了Test接口,并且可以包含其他Testsuites。它可以处理添加Test时抛出的所有异常。 Testsuite处理测试用例有6个规定(否则将被拒绝执行测试) ² 测试用例必须是公有类(Public) ² 用例必须继承TestCase类 ² 测试用例的测试方法必须是公共的( Public ) ² 测试用例的测试方法必须声明为Void ² 测试用例中测试方法的前置名词必须是test ² 测试方法在测试用例中误任何传输参数 Ø Testresult结果类和其他类别 Testresult结果类收集了任何测试的累积结果,每个测试的Run()方法都是通过Testresult实例传递的。如果Testresult执行Testcase失败,将异常抛出。如果Testresult执行Testcase失败,将异常抛出。 Testlistener接口是Testrunner类的事件监控规定。通知listener的对象相关事件,包括开始starttest的测试(Test test),endtest测试结束(Test test),错误添加异常adderoror(Test test,Throwable t)增加失败的addfailure(Test test,AssertionFailedError t)。 Testfailure失败类是一种“失败”状态的收集类,它解释了每次测试执行过程中的异常情况。 Testfailure失败类是一种“失败”状态的收集类,解释每次测试执行过程中的异常情况。简要描述了Tostring()方法返回“失败”状态

4、JUnit在Eclipse中的使用 测试对保证软件开发质量起着非常重要的作用,单元测试更是必不可少。Junit是一个非常强大的单元测试包,可以测试一个/多个类别的单个/多个方法,也可以将不同的Testcase组合成Testsuit,使测试任务自动化。 Eclipse还集成了Junit,可以非常方便地编写TestCase。Eclipse有一个Junit插件,可以在您的项目中开始测试相关类别,而无需安装,并可以调试您的测试用例和测试类别。 4.1、Juint在Eclipse中的使用步骤 Eclipseee环境如下步骤 SDK 3.2.2.Junit3.8.1 Ø 点击“创建一个新的测试用例或选择你想要测试的现有JAVA文件”File->New->菜单项或右击文件,弹出“New在对话框中选择“”JUnit Test Case进入“”,进入“New JUnit Test Case”对话框 Ø 在“New JUnit TestCase在对话框中填写相应的栏目,主要包括Name(测试用例名),SuperClass(如果Junit的版本是3.8.1.测试的超类一般默认为junit.framework.TestCase; 如果Junit版本是Junit 4.4,默认超类为java.lang.Object。),Class Under Test(被测类),Source Folder(保存测试用例的目录),Package(测试用例包名),是否自动生成main,setUp,tearDown方法。),Class Under Test(被测类),Source Folder(保存测试用例的目录),Package(测试用例包名),是否自动生成main,setUp,teardown方法。在这里,一般可以填写NAME,选择setupt和teardown。 Ø 点击“Next>“按钮,进入Testt, Methods,在这里,您可以直接检查您想要测试的测试方法,Eclipse将自动生成与选定方法相对应的测试方法,点击“Fishish在按钮后创建一个测试用例。 Ø 编写测试用例后,单击“Run"按钮可以看到操作结果。 补充:要使用Junit,您必须首先使用Junit JAR保存在项目的Build路径上,并创建一个测试类。在项目Build路径上保存Junit的步骤如下: 右击项目—>选择菜单底部的PropertiesJava Build Path—>选择Libraries选择Librariess—>点击Add Variable按钮—>如果没有,检查现有列表中是否有Junit文件,点击Configure Variable—>New按钮,输入JUNIT_LIB作为变量名,编辑变量并指向解压后的JUNIT目录中的一个,名为JUNIT.jar文件—>然后点击OK选择刚刚添加的jar文件。 4.2、JUnit在Eclipse中的应用示例 让我们在Eclipse中使用Junit测试HelloWorld 测试方法: Ø HelloWorld.sayHello()执行是否正常,结果是否符合要求 Ø HelloWorld.add()方法是否和我们预期的一样执行 下一步,我们准备测试这两种方法,以确保功能正常。HelloWorld选择.java,点击右键,选择New->JUnit Test Case: 进入下面的页面,这里已经填写了很多栏目,即需要测试的文件的相关信息。如果您想在测试后删除测试文件,您也可以更改路径。(本机在Eclipse现有JUnit3.8.在新版JUnit4.4的基础上,添加了一个新版本 点击Next进入Testttttet Methods,SayHello和add在这里选择要测试的方法。 点击Finish,最后编写完成测试的用例代码如下: Run直接运行->Run As->JUnit Test,JUnit测试结果可以看到: 绿色表示测试通过,只要一次测试失败,就会显示红色并列出未通过测试的方法。 5、后记 从以上角度来看,使用Junit并不难,但关键是完成测试码的最后一步,即编写TestCase。 5、后记 从以上角度来看,使用Junit并不难,但关键是完成测试码的最后一步,即编写Testcase。写一个好的Testcase并不容易。糟糕的Testcase往往既浪费时间,又起不到实际作用。相反,一个好的Testcase不仅可以指出代码中存在的问题,还可以作为代码更准确的文档,在连续集成的过程中起着非常重要的作用。在写TestCase时,我们需要注意几点: Ø 测试的独立性:一次只测试一个对象,以便定位错误的位置。有两层含义:一个Testcase,只测试一个对象;一个Testmethod,只测试这个对象中的一种方法。 Ø 给测试方法一个合适的名字。 一般来说,在添加Test之前,它被命名为原始方法名。 Ø 在assert函数中给出失败的原因,如:assertTrue( “… should be true”, ……),方便查错。在这个例子中,如果不能通过asserttrue,则显示给出的信息。在junit中,每个assert函数都有第一个参数,即出错时显示信息的函数原型。 Ø 测试所有可能导致失败的地方,如一个类中频繁变化的函数。对于那些只含getter/setter的类别,如果是由IDE(如eclipse)产生的,则可能是意想不到的;如果是手工写的,最好测试一下。 Ø setup和teardown中的代码不应与测试方法有关,而应与整体情况有关。在setup和teardown中,A和B的代码应该是A和B都需要的代码。 Ø 组织测试代码:相同的包,不同的目录。通过这种方式,测试代码可以访问测试类protected变量/方法,方便编写测试代码。将其放置在不同的目录中,便于测试代码的管理,以及代码的包装和发布。