zoukankan      html  css  js  c++  java
  • Java单元测试技术2

    1      测试桩构建(EasyMock

    构造测试桩太麻烦是项目组抱怨单元测试难做的主要原因之一,尤其是WEB应用程序开发,大量对象是由WEB容器生成,如HttpServletRequestHttpServletResponseServletContext等,只有将程序布署到服务器上才能获得这些对象,这样带来的麻烦是:一方面被测对象难于孤立,输入输出难以自由控制;另一方面每次运行都要将代码布署到服务器上很浪费时间,无法脱离服务器独立运行。目前构建测试桩的首选工具是EasyMock,使用它之前需要在CLASSPATH上加上它提供的JAR包。

    1)        EasyMock的原理

    EasyMock模拟对象的方法来自于JDK提供的对象代理机制,java.lang.reflect.Proxy类的静态方法newProxyInstance用于生成代理对象,以下是方法原型:

    Object Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

    其中的interfaces就是代理对象将要实现的接口,调用代理对象的方法后要执行的代码在InvocationHandler接口实现中定义。

    这儿的代理对象,被EasyMock用于模拟接口对象,我们称其为Mock对象

    2)        EasyMock的使用步骤

    EasyMock中,生成和使用Mock对象的步骤很简单:生成Mock对象-->录制-->回放-->验证。MockControl类是EasyMockFaçade对象,这个对象暴露了EasyMock的全部使用方法,也就是说你只需要关心这个类提供的方法即可。

    u  生成Mock对象

    以下代码生成了一个mock对象,类似于Java语言的new操作。但首先须获得MockControl对象,其中的Collaborator就是要模拟的接口对象。

    control = MockControl.createControl(Collaborator.class);

    mock = (Collaborator)control.getMock();

    u  录制

    获得Mock对象后默认处于录制状态,这时你可以根据你的预期指出将要调用到Mock对象的哪些方法、传给方法的参数对象是什么,进一步可以指定方法的返回值或者要求抛出一个异常。以下代码表示预期会调用到mock对象的voteForRemoval方法,传进来的参数是"Document"字符串,并且调用该方法后希望它返回值-42

    control.expectAndReturn(mock.voteForRemoval("Document"), -42);

    u  回放

    就是将Mock对象与被测单元关联,实施对被测单元的调用。调用到Mock对象的预期方法后就按录制时给出的方法返回值返回。以下语句表示开始回放:

    control.replay();

    u  验证

    EasyMock能够验证的内容包括:在被测单元执行中是否按录制时列出的方法预期调用到了、传给方法的参数值是否也是预期的。只需要调用以下语句即可:

    control.verify();

    如果验证失败则会抛出JUnit定义的异常AssertionFailedError,表示用例执行失败。

    3)        “类”对象模拟

    EasyMock不仅可以模拟“接口”对象,还可以模拟“类”对象,用到的主要类是MockClassControl,该类继承自MockControl,使用方法同MockControl,也就是将前面用到MockControl的地方换成MockClassControl即可。如下例:

    MockControl ctrl = MockClassControl.createControl(ToMock.class);

    ToMock mock = (ToMock) ctrl.getMock();

    也可以只模拟部分方法,以下代码表示只模拟ToMock类的无参数方法mockedMethod

    MockControl ctrl = MockClassControl.createControl(ToMock.class, new Method[] { ToMock.class.getDeclaredMethod("mockedMethod", null) } );

    4)        新版EasyMock2.0

    EasyMock也有新旧版本的区别,EasyMock2.0后使用了JDK5.0的新特性泛形类(generic class)。2.0版本的使用,大的步骤同前,主要有以下特点:

    u  主要使用了JDK5.0Generic Class

    u  直接使用org.easymock.EasyMock提供的静态方法,可在文件开始静态引入:

    import static org.easymock.EasyMock.*;

    Ø  createMock方法用于直接生成Mock对象,省去了先获得MockControl对象的麻烦,如下例:

    mock = createMock(Collaborator.class);

    Ø  replay方法和verify方法可通过参数指定多个Mock对象

    replay(requestObj, contextObj, dispatcherObj);

    Ø  expect用于指定方法的期望返回值、调用次数、期望抛出的异常等,如下例:

    expect(mock.voteForRemoval("Document")).andReturn((byte) -42);

    u  验证失败抛出JDK自带的AssertionError异常

    u  可在多个Mock对象间验证方法调用顺序

    u  方法的参数比较规则是以方法调用形式实现的,另外还可以自定义比较器

    2      数据库数据初始化与测试验证(DBUnit

    前面已经提到,业软的很多产品访问数据库的代码量占很大比例,目前各版本开发中访问数据库的技术有很多,如JDBCEntityBeanHibernateSpringiBATIS等,不管你使用哪种技术,实际上归根结蒂都是通过Java代码访问数据库,对于这些访问数据库的代码的单元测试长期以来存在以下问题:首先,如果将数据库层用取代,一方面构建的工作量巨大,另一方面即使能够构建完成,其实也不太容易发现代码中的BUG,因为隔离了数据库后其实代码逻辑就比较简单了,综合考虑,我们的建议是只有真实地连数据库才能真正有效地对单元做测试;但真实地连数据库又会带来另外两个问题:

    u  如何确保每个用例执行前的数据库环境是可预期的?也就是数据库的初始化问题。

    u  如何确保用例执行过程中正确地操作了数据库?也就是用例执行后的数据库验证。

    通过在项目组的推广试用,我们发现DBUnit很好地解决了这两个问题,它的主要功能包括测试前初始化数据库数据,测试结束后验证数据库数据,另外,DBUnit提供有自定义ANT任务,结合ANT实现前述功能。要使用DBUnit也是只需要在你的CLASSPATH路径中加上它的JAR包。

    1)        测试用例框架

    DBUnit提供有基础类DBTestCase,该类继承自JUnitTestCase,写测试用例的方法同JUnit,下面列出我们自己写的测试用例的基本框架,余下的任务只是增加测试方法。

    public class MyDBAccessTest extends DBTestCase

    {

        private MyDBAccess access = null;//这是被测类,含有要测的访问数据库的方法

       

        static

        {//请参见说明一

           System.setProperty(             PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS,        "oracle.jdbc.driver.OracleDriver");

           System.setProperty(      PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL,           "jdbc:oracle:thin:@10.164.22.163:1521:ise");

           System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "tzs21911"); 

           System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "tzs21911");

           System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_SCHEMA,   "TZS21911");// 必须大写

        }

       

        protected void setUp() throws Exception

        {

           super.setUp();

                  access = new MyDBAccess();

        }

       

        protected void tearDown() throws Exception

        {

           super.tearDown();

           this.getConnection().close();

           access = null;

        }

          

        @Override

        protected IDataSet getDataSet() throws Exception

        {//请参见说明二

           return new FlatXmlDataSet(new FileInputStream("dataset.xml"));

        }

       

        @Override

        protected DatabaseOperation getSetUpOperation() throws Exception

        {//请参见说明三

           return DatabaseOperation.CLEAN_INSERT;

        }

    }

    u  说明一

    设置要连接的数据库属性,依次包括:JDBC驱动程序类、被访问的数据库的URL、数据库登录用户名、口令、数据库SCHEMA,这些参数用于创建数据库连接。这里是通过系统属性(System Property)的形式告诉DBUnit的,也是默认方式,另外的方式还有DataSource方式、JNDI方式,如果需要请参考javadoc文档。这里要注意的是ORACLE数据库下必须要提供有SCHEMA,且必须是字母大写,如果省略SCHEMA,则要将你的数据库连接权限配置成只能访问当前用户的SCHEMA

    u  说明二

    这是必须要实现的模板方法(template method),目的是告诉DBUnit要初始化数据库的数据,数据的描述方式有多种,常用的称为FlatXml格式,其中的dataset.xml就是这种格式的文件名,下面是这个文件的例子:

    <?xml version='1.0' encoding='UTF-8'?>

    <dataset>

    <USERS ID='1' NAME='taozs1' AGE="31"/>

    <USERS ID='2' NAME='taozs2' AGE="32"/>

    <USERS ID='3' NAME='taozs3' AGE="33"/>

    </dataset>

    <dataset>元素间的每一行表示要初始化的一条记录,其中USERS是表名,IDNAMEAGE是表字段,=后面表示字段值。可以在一个文件中初始化多个表,但如果表之间有约束关系(如外键),要注意表的先后顺序。

    该方法返回数据集(DataSet),注意数据集可以是多个表的集合

    u  说明三

    这是告诉DBUnit在执行用例前(测试函数运行前,在setUp方法里被调用到)要执行的数据库初始化操作(Operation),数据的来源就是前面提到的getDataSet方法返回的数据集。有多个操作类型,常用的有:

    DatabaseOperation.INSERT

    往数据库表里插入数据集中的数据,前提条件是数据库表里没有这些数据。

    DatabaseOperation.DELETE

    删除数据库里数据集包含的记录,注意数据集里不含有的记录不删除。

    DatabaseOperation.DELETE_ALL

    删除数据库里数据集指定的表的所有记录。

    DatabaseOperation.CLEAN_INSERT

    先执行DELETE_ALL操作,再执行INSERT操作,这是最常用的操作。

    2)        数据库验证

    在运行了被测方法后,我们需要验证该方法对数据库的操作是否正确,以下是DBUnit提供的类的两个静态方法,用于验证数据库实际结果与预期结果是否一致,如果不一至,会报用例执行不通过的错误。预期结果也是以FlatXml文件格式表示的。

    public class Assertion

    {

        public static void assertEquals(ITable expected, ITable actual)

        public static void assertEquals(IDataSet expected, IDataSet actual)

    }

    下面是一个验证的例子:

        public void testMe() throws Exception

        {

            // 调用被测单元操作数据库

            ...

     

            // 之后从数据库取回数据

            IDataSet databaseDataSet = getConnection().createDataSet();

            ITable actualTable = databaseDataSet.getTable("TABLE_NAME");

     

            //FlatXml格式的数据集中取回预期数据

            IDataSet expectedDataSet = new FlatXmlDataSet(new File("expectedDataSet.xml"));

            ITable expectedTable = expectedDataSet.getTable("TABLE_NAME");

     

            // 验证实际数据与预期数据是否一致

            Assertion.assertEquals(expectedTable, actualTable);

        }

    注意也可以对数据集进行验证,也就是上面的第二个方法:assertEquals(IDataSet expected, IDataSet actual)

    3)        使用查询获得数据库快照

    你也可以通过查询获得数据库实际数据,以下是例子:

    ITable actualJoinData = getConnection().createQueryTable("RESULT_NAME",

                    "SELECT * FROM TABLE1, TABLE2 WHERE ...");

    4)        比较时忽略某些列(字段)

    对于主键字段或日期时间字段,可能是由程序动态生成,无法预期它的值,所以在比较时可以告诉DBUnit不对这些字段验证,以下是例子:

        ITable filteredTable = DefaultColumnFilter.includedColumnsTable(actual,

                expected.getTableMetaData().getColumns());

        Assertion.assertEquals(expected, filteredTable);

    也就是在你的预期FlatXml文件中只需列出你关心的字段值即可

    5)        DBUnitANT的集成

    DBUnitANT能够集成,DBUnit提供有ANT自定义任务(TASK)扩展,这个DBUnit任务能够完成数据库初始化、数据库数据验证、数据库数据导出成XML文件等功能,这里我们以如何由当前数据库数据自动导成FlatXml文件的例子作说明,其它功能请参见javadoc

    手工编写FlatXml文件可能比较繁琐,DBUnit提供由当前数据库数据自动导出FlatXml文件的功能,之后我们可以基于这个导出的文件修改即可。导出的方式有两种,一种是通过编码实现;另一种是调用ANT任务。以下是调用ANT任务的使用说明:

    u  定义dbunit任务

    (1)       DbUnit jar文件加到antlib目录下

    (2)       Build文件的开始处添加以下行

    <taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask"/>

    u  调用dbunit任务

         <dbunit driver="oracle.jdbc.driver.OracleDriver"

                     url="jdbc:oracle:thin:@10.164.22.163:1521:ise"

                     userid="tzs21911"

                     password="tzs21911"

                        schema="TZS21911"

                        classpath="C:/eclipse/lib/ojdbc14.jar">

                 <export dest="export.xml"/>

             </dbunit>

    上面的例子完成了这么几件事:通过dbunit元素属性告诉DBUnit要连接的数据库属性、通过export元素要求DBUnit导出数据库数据,属性dest是导出的文件名,属性format是导出的文件格式,默认格式是FlatXml

    6)        小结

    在每个用例执行前,一般我们需要提供两个FlatXml文件,一个用于数据库初始化,一个用于用例执行后的数据库验证(预期结果)。我们在写自己的测试用例代码时注意以下几点:

    u  继承自DBTestCase

    u  设置数据库连接属性

    u  实现getDataSet模板方法

    u  getSetUpOperation中指定初始化操作

    u  调用类Assertion的方法进行验证

    还要指出的是我们也可以不需要继承自DBTestCase也能完成数据库的测试,详细方法就不讲了,请大家参考我的例子代码(类dbunit.NoneDBTestCaseTest)。

     

  • 相关阅读:
    java实现调用打印机代码
    java合并PDF文件
    关于如何把项目做得更好的一次思考
    web语义化之SEO和ARIA
    快速理解web语义化
    使用HTML5地理位置定位到城市的方法及注意事项
    Plupload上传插件简单整理
    两列布局——左侧宽度固定,右侧宽度自适应的两种方法
    Java并发编程之线程基础
    Spring Boot学习之YAML文件配置
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3089231.html
Copyright © 2011-2022 走看看