使用 JdbcProxy 测试 Java 应用程序

来源:developerWorks 中国 作者:郑 溪龙
  
JdbcProxy 可以理解为 JDBC 代理,是一个开源的 JDBC 项目。它架构在 JDBC Driver 之上,能够记录一个 Java 应用的数据库访问过程,并将这个过程重现出来。本文将以一个具体的 Java 应用为例,演示如何使用 JdbcProxy 记录和重现 JDBC 访问,实现 Java 应用的测试工作。

在我们测试 Java 应用程序时,往往需要连接数据库,并从数据库中获得准确的测试数据用以测试应用程序是否正确。然而准备测试数据的工作较为复杂,一旦数据库中的数据发生变化,要想恢复到之前的版本也很费时。对于那些没有条件连接数据库的测试者而言,测试工作是不能进行下去的。因此,如果可以为某个待测应用准备一套完备的测试数据,让程序开发、测试人员在不依赖于具体数据库的情况下对应用进行测试,这无疑是十分方便的。

简介

JdbcProxy 是 SourceForge 上一个开源的 Java 项目,用 Java 语言编写,遵循 LGPL 和 MPL1.1 协议,由 Frans van Gool 开发,支持 JDBC2.0 规范。通过继承和重写 JDBC2.0 的接口,将一个 Java 应用访问数据库的过程记录在 XML 文件中,并通过这些文件在脱离数据库的情况下重现这个调用过程。 JdbcProxy 可以用在 Java 应用程序的测试中,进行数据准备并模拟数据库调用过程。读者可以从 JdbcProxy 主页 获得最新的程序源代码以及说明文档。目前最新的版本是 1.1 。

使用 JdbcProxy 代替普通的数据库调用可以满足程序开发、测试人员的很多需求,使准备测试数据的工作变得简单。以文章查询系统为例,有些测试用例需要测试当数据库中没有数据时页面的显示情况——显示没有相应数据的页面;有些测试用例需要测试当数据库中只有一条数据时页面的显示情况——显示文章的内容而不是文章的列表;还有些测试用例需要测试页面的分页效果,这时就需要为程序准备不同的测试数据。如果采用直接连接测试用数据库的方式进行测试,不同的测试用例需要重新准备测试数据库,操作起来比较复杂,也不能同时测试不同的测试场景。如果使用 JdbcProxy,就可以为同一个 Java 程序准备不同的测试用数据文件,测试者可以脱离后端数据库的限制,只需要访问到数据文件就能完成测试。不同测试人员能够彼此不受影响的同时测试这个应用,从而大大简化了准备测试环境的过程。

使用 JdbcProxy

JdbcProxy 提供了两种记录 JDBC 调用过程的方式,一种是方便开发人员和测试人员阅读的,另一种是用于回放 JDBC 调用过程的。

第一种方式主要是为了让开发人员和测试人员能够了解 Java 应用调用 JDBC 的详细信息,当应用程序较为复杂——例如进行了多表查询,采用这种记录方式可以让开发人员和测试人员一目了然的看到应用程序访问数据库的过程。

第二种方式是为测试程序准备数据的关键步骤之一。为了回放 JDBC 调用过程,JdbcProxy 分别处理应用程序对数据库的每个请求 (request) 与响应 (response),并在指定目录下生成一系列 XML 文件,这些文件是回放 JDBC 调用过程的基础。这些 request/response 文件并不能直接被 JdbcProxy 调用,JdbcProxy 还提供了一个 StubTraceMerger 工具,用来将这些 request/response 文件整合在一个文件中,这个整合的文件就是我们测试 Java 应用所需要的数据文件。

JdbcProxy 提供了一种通过 HTTP 服务器发送请求并接收响应的机制,这些请求和响应都按照应用程序的 JDBC 调用顺序记录在整合文件中,当 Java 应用试图通过 JDBC 连接数据库时,JdbcProxy 会通过 HTTP 服务器从整合文件中读取相应的数据,模拟调用数据库的过程,使用户得以在脱离数据库的情况下进行 Java 应用的测试。

因此要想利用 JdbcProxy 测试 Java 应用程序,首先需要生成用于回放 JDBC 调用过程的 request/response 文件,然后将这些 request/response 文件整合在一个文件中,最后将这个整合的数据文件提供给 HTTP 服务器,这样 JdbcProxy 就可以通过 HTTP 服务器访问到数据文件,实现脱离数据库进行测试的目的。

下面将对 JdbcProxy 提供的每个功能做详细的介绍。

记录方便开发人员和测试人员阅读的 JDBC 调用过程

JdbcProxy 可以跟踪 JDBC 调用过程,生成方便开发人员和测试人员阅读的文件格式。在普通的数据库调用中,如果开发人员和测试人员想了解 JDBC 的调用过程,或者需要完整的查看某个 SQL 语句,则需要手工将这些信息打印出来或者记录在日志中。 JdbcProxy 的这个功能就像为 JDBC 的调用过程自动记录了一个日志,开发人员和测试人员可以从日志中获得被调用的每一个方法,以及调用方法的参数和返回值。这样便于开发人员和测试人员发现由于 SQL 语句拼写错误而导致的数据库访问失败,或是由于数据库中没有数据而导致的空指针异常,使应用程序的数据库访问过程更加直观。可以根据清单 1中的示例代码生成 JDBC 调用过程。


清单 1
				
import java.sql.Statement; 

public class JdbcProxyDemo { 
    public static void main(String args[]) throws Exception {
        String driver = "nl.griffelservices.proxy.jdbc.oracle.StubTracerDriver";
        String url = "jdbc:tracer::COM.ibm.db2.jdbc.app.DB2Driver:jdbc:db2:SAMPLE";
        Class.forName(driver); 
        Connection connection = DriverManager.getConnection(url); 
        Statement stmt = connection.createStatement(); 
        stmt.executeUpdate("DELETE FROM greetings WHERE greeting='Good Night'");//delete
        stmt.executeUpdate("INSERT INTO greetings VALUES('Good Evening')"); //insert 
        stmt.executeUpdate("UPDATE greetings SET greeting = 'Good Night' 
            WHERE greeting = 'Good Evening'"); //update
        ResultSet rs = stmt.executeQuery("SELECT * FROM greetings");//select
        while (rs.next()) { 
            System.out.println(rs.getString("greeting")); 
        } 
        rs.close(); 
        stmt.close(); 
        connection.close(); 
    } 
 }

与普通数据库调用不同,清单 1中示例程序中所需的数据库驱动是 JdbcProxy 提供的StubTracerDriver,URL 的结构如下:

jdbc:tracer:<filename>:<driver>:<url>

其中:

  • <filename> 是保存结果的文件名;如果为空,结果会被输出到控制台。
  • <driver> 是数据库驱动名,该示例程序使用的是 db2 的驱动。
  • <url> 是数据源的 URL,该示例程序使用 jdbc:db2:SAMPLE,不需要用户名和密码。

运行上述程序得到清单 2的记录结果:


清单 2
				
//记录调用 getConnection 方法java.sql.Driver.connect(jdbc:db2:SAMPLE, {}) returns 
 DB2Connection 
 { 
    connectionHandle = 1 
    SPConnected = false 
    source = SAMPLE 
    user = 
    conArgs = 
    closed = false 
    describeCached = false 
    describeParam = true 
    isReadOnly = false 
    autoClose = false 
    LONGDATA compat = false      
 } 

 //记录建立的 Connection 对象
 java.sql.Connection.<init>( 
 DB2Connection 
 { 
    connectionHandle = 1 
    SPConnected = false 
    source = SAMPLE 
    user = 
    conArgs = 
    closed = false 
    describeCached = false 
    describeParam = true 
    isReadOnly = false 
    autoClose = false 
    LONGDATA compat = false 
 } 
 ) 
 //记录调用 createStatement 方法
 java.sql.Connection.createStatement() returns 
 DB2Statement 
 { 
    Connection -> DB2Connection 
    Connection -> { 
    Connection ->     connectionHandle = 1 
    Connection ->     SPConnected = false 
    Connection ->     source = SAMPLE 
    Connection ->     user = 
    Connection ->     conArgs = 
    Connection ->     closed = false 
    Connection ->     describeCached = false 
    Connection ->     describeParam = true 
    Connection ->     isReadOnly = false 
    Connection ->     autoClose = false 
    Connection ->     LONGDATA compat = false 
    Connection -> } 

    ResultSet -> null 

    statementHandle = 1:1 
    SQL = null 
    maxRows = 0 
    maxFieldSize = 0 
    rowCount = 0 
    colCount = 0 
    closed = false 
    internalStmt = false 
    returnCode = 0 
 } 

 //记录建立的 Statement 对象
 java.sql.Statement.<init>( 
 DB2Statement 
 { 
    Connection -> DB2Connection 
    Connection -> { 
    Connection ->     connectionHandle = 1 
    Connection ->     SPConnected = false 
    Connection ->     source = SAMPLE 
    Connection ->     user = 
    Connection ->     conArgs = 
    Connection ->     closed = false 
    Connection ->     describeCached = false 
    Connection ->     describeParam = true 
    Connection ->     isReadOnly = false 
    Connection ->     autoClose = false 
    Connection ->     LONGDATA compat = false 
    Connection -> } 

    ResultSet -> null 

    statementHandle = 1:1 
    SQL = null 
    maxRows = 0 
    maxFieldSize = 0 
    rowCount = 0 
    colCount = 0 
    closed = false 
    internalStmt = false 
    returnCode = 0 
 } 
 ) 
 //执行删除操作,操作成功,返回值是 1
 java.sql.Statement.executeUpdate(DELETE FROM greetings WHERE greeting='Good Night') 
 returns 1 
 //执行添加操作,操作成功,返回值是 1
 java.sql.Statement.executeUpdate(INSERT INTO greetings VALUES('Good Evening')) 
 returns 1 
 //执行修改操作,操作成功,返回值是 1
 java.sql.Statement.executeUpdate(UPDATE greetings SET greeting = 'Good Night' 
     WHERE greeting = 'Good Evening') 
 returns 1 
 //执行查询操作并返回结果集
 java.sql.Statement.executeQuery(SELECT * FROM greetings) returns 
 DB2ResultSet 
 { 
    Statement -> DB2Statement 
    Statement -> { 
    Statement ->     Connection -> DB2Connection 
    Statement ->     Connection -> { 
    Statement ->     Connection ->     connectionHandle = 1 
    Statement ->     Connection ->     SPConnected = false 
    Statement ->     Connection ->     source = SAMPLE 
    Statement ->     Connection ->     user = 
    Statement ->     Connection ->     conArgs = 
    Statement ->     Connection ->     closed = false 
    Statement ->     Connection ->     describeCached = false 
    Statement ->     Connection ->     describeParam = true 
    Statement ->     Connection ->     isReadOnly = false 
    Statement ->     Connection ->     autoClose = false 
    Statement ->     Connection ->     LONGDATA compat = false 
    Statement ->     Connection -> } 

    Statement ->     statementHandle = 1:1 
    Statement ->     SQL = SELECT * FROM greetings 
    Statement ->     maxRows = 0 
    Statement ->     maxFieldSize = 0 
    Statement ->     rowCount = 0 
    Statement ->     colCount = 0 
    Statement ->     closed = false 
    Statement ->     internalStmt = false 
    Statement ->     returnCode = 0 
    Statement -> } 

    numCols = 1 
    mappedRS = false 
    nullTag = true 
    closed = false 
    maxFieldSize = 0 
    returnCode = 0 
    returnLen = 0 
    colTypes[0] = 0 
    colSizes[0] = 0 
 } 

 //记录建立的 ResultSet 对象
 java.sql.ResultSet.<init>( 
 DB2ResultSet 
 { 
    Statement -> DB2Statement 
    Statement -> { 
    Statement ->     Connection -> DB2Connection 
    Statement ->     Connection -> { 
    Statement ->     Connection ->     connectionHandle = 1 
    Statement ->     Connection ->     SPConnected = false 
    Statement ->     Connection ->     source = SAMPLE 
    Statement ->     Connection ->     user = 
    Statement ->     Connection ->     conArgs = 
    Statement ->     Connection ->     closed = false 
    Statement ->     Connection ->     describeCached = false 
    Statement ->     Connection ->     describeParam = true 
    Statement ->     Connection ->     isReadOnly = false 
    Statement ->     Connection ->     autoClose = false 
    Statement ->     Connection ->     LONGDATA compat = false 
    Statement ->     Connection -> } 

    Statement ->     statementHandle = 1:1 
    Statement ->     SQL = SELECT * FROM greetings 
    Statement ->     maxRows = 0 
    Statement ->     maxFieldSize = 0 
    Statement ->     rowCount = 0 
    Statement ->     colCount = 0 
    Statement ->     closed = false 
    Statement ->     internalStmt = false 
    Statement ->     returnCode = 0 
    Statement -> } 

    numCols = 1 
    mappedRS = false 
    nullTag = true 
    closed = false 
    maxFieldSize = 0 
    returnCode = 0 
    returnLen = 0 
    colTypes[0] = 0 
    colSizes[0] = 0 
 } 
 ) 
 //执行 rs.next() 方法,返回值是 true
 java.sql.ResultSet.next() returns true 
 //执行 rs.getString() 方法,返回对应列的值
 java.sql.ResultSet.getString(greeting) returns Good Night 
 //执行 System.out.println Good Night 
 java.sql.ResultSet.next() returns true 
 java.sql.ResultSet.getString(greeting) returns Good Morning 
 Good Morning 
 java.sql.ResultSet.next() returns true 
 java.sql.ResultSet.getString(greeting) returns Good Afternoon 
 Good Afternoon 
 //执行 rs.next() 方法,返回值是 false
 java.sql.ResultSet.next() returns false 
 //关闭 ResultSet
 java.sql.ResultSet.close() 
 //关闭 Statement
 java.sql.Statement.close() 
 //关闭 Connection
 java.sql.Connection.close()

可以看到,JdbcProxy 记录下了每一个 JDBC 调用的参数和返回值,可以很容易的通过这些值判断出程序在什么地方出现问题。如果运行过程中出现异常,JdbcProxy 还可以将抛出的异常记录下来。

记录用于回放的 JDBC 调用过程

方便开发人员和测试人员阅读的 JDBC 调用过程其记录形式不便于进行解析,因此并不能直接被用来回放的 JDBC 调用过程。为了简化准备数据的过程,JdbcProxy 还可以生成一种用于回放 JDBC 调用过程的,特别是易于在 HTTP 服务器上使用的记录文件。在运行新的程序之前,我们首先需要新建一个空的文件夹 output 用来存放 JdbcProxy 生成的一系列 request/response 文件,然后将 url 做以下修改,如 清单 3 所示。


清单 3
				
String url = "jdbc:stubtracer:output:COM.ibm.db2.jdbc.app.DB2Driver:jdbc:db2:SAMPLE";

URL 结构如下:

jdbc:stubtracer:<foldername>:<driver>:<url>

其中:

  • <foldername> 是输出 request/response 文件的位置;如果为空则将文件内容输出到控制台。
  • <driver> 是数据库驱动名,该示例程序使用的是 DB2 的驱动。
  • <url> 是数据源的 URL,该示例程序使用 jdbc:db2:SAMPLE,不需要用户名和密码。

运行上述程序会在指定目录下生成 32 个以 request/response 开头的文本文件,如图 1所示。


图 1. request/response 文件
图 1. request/response文件

这些文件包含了回放 JDBC 调用过程所需的所有请求和响应。以建立 Connection 的请求为例,该示例程序对数据库的第一个请求就是建立 Connection,这个过程记录在 request_0_0.txt 文件中,参见清单 4。其中文件名中的 2 个“ 0 ”分别代表本次 JDBC 调用的 id 和 status,调用的方法是 connect 。


清单 4
				
<?xml version="1.0" encoding="UTF-8"?> 
 <request> 
   <class>java.sql.Driver</class> 
   <id>0</id> 
   <status>0</status> 
   <method>connect</method> 
   <parameter> 
      <class>java.lang.String</class> 
      <value>jdbc:db2:SAMPLE</value> 
   </parameter> 
   <parameter> 
      <class>java.util.Properties</class> 
      <value>{language=C}</value> 
   </parameter> 
 </request>

JdbcProxy 会根据 request 文件中 id 和 status 的值找到对应的 response 文件,在本例中为 response_0_0.txt,参见清单 5。在 response_0_0.txt 中,我们看到已经新建了一个 java.sql.Connection 的对象,并且指定了下一个请求的 id 和 status 。


清单 5
				
<?xml version="1.0" encoding="UTF-8"?> 
 <response> 
   <newstatus>1</newstatus> 
   <returnvalue> 
      <nl.griffelservices.proxy.stub.ProxyObject> 
         <class>java.sql.Connection</class> 
         <id>1</id> 
         <status>0</status> 
      </nl.griffelservices.proxy.stub.ProxyObject> 
   </returnvalue> 
 </response>

整合 request/response 文件

在上面生成的 request/response 文件还需要进行整合才能提供给 HTTP 服务器用于回放 JDBC 调用过程。可以使用 StubTraceMerger 工具将一系列 request/response 文件整合成一个文件。整合文件需要运行的命令如清单 6所示。


清单 6
				
StubTracerMerger output

该命令的结构如下:

StubTracerMerger <foldername> [ <id> [ <status> ] ]

其中:

  • <foldername> 是 request/response 文件存放的位置,不可为空。
  • <id> 是需要整合的初始响应的 id,默认值为 0,即整合完整的 JDBC 调用过程。
  • <status> 是需要整合的初始响应的 status,默认值为 0 。

运行上述命令,StubTracerMerger 会将所有 request/response 文件的内容按照调用的顺序整合在一个 <response> 元素下,并在控制台上输出一段整合的 XML 文件。从 XML 文件中,我们能够看到 StubTracerMerger 将新建的 Connection,Statement 以及 ResultSet 对象记录在 ProxyObject 结点中,调用的方法则被记录在 requestresponse 结点。例如:java.sql.Connection 调用了 createStatement() 以及 close() 两个方法,所以 Connection 的 ProxyObject 结点中包含了两个 requestresponse 子结点;生成的 java.sql.Statement 分别进行了删除、添加、修改、查询,以及关闭 Statement 操作,因此该结点包含 5 个 requestresponse 子结点。其中 id 代表对象的标识,status 代表该对象状态,初始值为 0 。按照这种方式,JdbcProxy 将 Java 应用程序调用 JDBC 访问数据库的过程记录成了一个 XML 文件。清单 7中给出一段执行删除操作的示例,读者可以在文章最后的 下载 区下载到完整的 XML 文件。


清单 7
				
<?xml version="1.0" encoding="UTF-8"?> 
 <response> 
   <newstatus>1</newstatus> 
   <returnvalue> 
      <nl.griffelservices.proxy.stub.ProxyObject> 
         <!--初始化 Connection 对象--> 
         <class>java.sql.Connection</class> 
         <!--Connection 对象的 id 是 1--> 
         <id>1</id> 
         <!--Connection 对象的 status,初始值为 0--> 
         <status>0</status> 
         <requestresponse> 
            <request> 
               <!--第一次使用 Connection 对象,调用 createStatement 方法,因此 status 赋值 0--> 
               <status>0</status> 
               <!--调用 createStatement 方法--> 
               <method>createStatement</method> 
            </request> 
            <response> 
               <!--Connection 对象下一次被使用时 status 的值--> 
               <newstatus>1</newstatus> 
               <returnvalue> 
                  <nl.griffelservices.proxy.stub.ProxyObject> 
                     <!--初始化 Statement 对象--> 
                     <class>java.sql.Statement</class> 
                     <!--Statement 对象的 id 是 2--> 
                     <id>2</id> 
                     <!--Statement 对象的 status,初始值为 0--> 
                     <status>0</status> 
                     <!--执行删除操作的代码--> 
                     <requestresponse> 
                        <request> 
                        <!--第一次使用 Statement 对象,调用 executeUpdate 方法,因此 status 赋值 0--> 
                           <status>0</status> 
                           <!--调用 executeUpdate 方法--> 
                           <method>executeUpdate</method> 
                           <!--executeUpdate 方法的参数--> 
                           <parameter> 
                              <class>java.lang.String</class> 
                              <!--执行删除的 SQL 语句--> 
                              <value>DELETE FROM greetings 
                                           WHERE greeting='Good Night'</value> 
                           </parameter> 
                        </request> 
                        <response> 
                           <newstatus>1</newstatus> 
                           <!--执行删除操作成功,返回值为 1--> 
                           <returnvalue> 
                              <int>1</int> 
                           </returnvalue> 
                        </response> 
                     </requestresponse> 

                     <!--省去执行插入、更新、查询操作的代码--> 

                     <requestresponse> 
                        <request> 
                           <status>4</status> 
                           <!--关闭 Statement 对象--> 
                           <method>close</method> 
                        </request> 
                        <response> 
                           <newstatus>5</newstatus> 
                           <returnvalue/> 
                        </response> 
                     </requestresponse> 
                  </nl.griffelservices.proxy.stub.ProxyObject> 
               </returnvalue> 
            </response> 
         </requestresponse> 
         <requestresponse> 
            <request> 
               <status>1</status> 
               <!--关闭 Connection 对象--> 
               <method>close</method> 
            </request> 
            <response> 
               <newstatus>2</newstatus> 
               <returnvalue/> 
            </response> 
         </requestresponse> 
      </nl.griffelservices.proxy.stub.ProxyObject> 
   </returnvalue> 
 </response>

通过 HTTP 服务器回放 JDBC 调用过程

JdbcProxy 提供了通过 HTTP 服务器回放 JDBC 调用过程的方式,即用户只需要将准备好的数据文件提供给 HTTP 服务器就可以在脱离数据库的情况下模拟对数据库的访问,完成 Java 应用的测试。如果想通过 HTTP 服务器回放 JDBC 调用过程,只需要修改示例程序中的 url,如清单 8所示。


清单 8
				
String url = "jdbc:stub:localhost:80:1000";

URL 结构如下:

jdbc:stub:<hostname>:<port>:<timeout>

其中:

  • <hostname> 是 HTTP 服务器的主机名,本地机器为 localhost 。
  • <port> 是 HTTP 服务器的端口号。
  • <timeout> 是超时时间,单位是毫秒。

在该示例程序中我们采用 Apache 的 HTTP server 2.2.4,使用默认端口 80 。用整合的数据文件替换 \htdocs 目录下的默认欢迎页面,这样 HTTP 服务器就可以从本地 80 端口接收请求并在数据文件中匹配到相应的 response,将 response 内容返回给 Java 应用程序。我们可以在控制台得到与直接连接数据库时相同的返回结果,JdbcProxy 可以通过预先准备好的数据文件,在不连接数据库的情况下通过 HTTP 服务器获得准确的响应数据,测试 Java 应用程序是否能够正确执行。

开发人员和测试人员可以通过修改整合的数据来获得不同的测试场景,例如按照整合文件的数据格式添加新的数据、删除多余的数据、或是修改数据的类型,用来构建不同的测试用例,满足不同测试的需要。

改进 JdbcProxy

在整合 request/response 文件时,JdbcProxy 只能将整合的数据文件输出到控制台,这种输出方式非常不利于在回放过程中使用。如果可以直接将其输入到 \htdocs 目录下的默认文件中,则省去了每次移动文件的麻烦。另外,JdbcProxy 只提供通过 HTTP 服务器回放 JDBC 调用过程的方式,必须有 HTTP 服务器的支持。因此,我们对 JdbcProxy 进行了一些改进,首先可以将整合的文件直接输出到指定目录下,其次可以不依靠 HTTP 服务器直接从整合文件中获取 JDBC 响应。

下面将介绍如何直接生成整合文件以及通过整合文件回放 JDBC 调用过程。

直接生成整合文件

在生成整合文件时,我们可以省去一系列生成 request/response 文件的步骤,直接将每个 request 和 response 存储于一个静态的 TreeMap 中,再将 TreeMap 中的内容整合并按照指定路径输出到文件中。这样做可以大大节省依次读取每个 request/response 文件所需要的时间,提高程序运行的效率。同时,可以自行指定整合文件的输出位置,不需要手动将整合的数据文件内容从控制台复制到 \htdocs 目录下的默认欢迎页面,在一定程度上减少了工作量。此处的改动包括:修改了 StubTracerHandler 类的 invoke 方法,在 nl.griffelservices.proxy.stub 包下增加了 FileStubTracerMerger 类,demo 包下增加了 GenerateMergerFile 类。

在运行程序之前,需要先将 StubTracerHandler 类的 invoke 方法做如下修改:

tracer.trace(request, response);

改成

new FileStubTracerMerger(map,request,response);

以便直接从内存中读取 response 生成整合文件。此外还需要运行下面程序,如清单 9所示。


清单 9
				
import java.sql.Statement; 
public class JdbcProxyDemo { 
    public static void main(String args[]) throws Exception {
        String driver = "nl.griffelservices.proxy.jdbc.oracle.StubTracerDriver"; 
        String url = "jdbc:stubtracer::COM.ibm.db2.jdbc.app.DB2Driver:jdbc:db2:SAMPLE";
        Class.forName(driver); 
        Connection connection = DriverManager.getConnection(url); 
        Statement stmt = connection.createStatement(); 
        stmt.executeUpdate("DELETE FROM greetings WHERE greeting='Good Night'");//delete
        stmt.executeUpdate("INSERT INTO greetings VALUES('Good Evening')");//insert
        stmt.executeUpdate("UPDATE greetings SET greeting = 'Good Night' 
            WHERE greeting = 'Good Evening'"); //update
        ResultSet rs = stmt.executeQuery("SELECT * FROM greetings");//select
        while (rs.next()) { 
            System.out.println(rs.getString("greeting")); 
        } 
        rs.close(); 
        stmt.close(); 
        connection.close(); 
 
        //将整合数据文件输出到 output/mergerfile.xml
        GenerateMergerFile mergerfile = new GenerateMergerFile("output/mergerfile.xml");  
        mergerfile.generateFile(); 
    } 
 }

其中所需要的 driver 与前面记录用于回放的 JDBC 调用过程中的 driver 相同,url 则不需要提供 foldername 。另外还要在程序的最后加入两行代码,GenerateMergerFile 方法所需的参数即整合文件的相对路径和文件名。运行上述程序,在 output 目录下会生成名为 mergerfile.xml 的整合文件,mergerfile.xml 即为测试 Java 应用程序所准备的数据文件。

通过整合文件回放调用过程

对于直接通过整合文件回放 JDBC 调用过程的问题,我们重写了 JdbcProxy 中的一些方法。依然使用前面的示例程序,如果采用这种方式回放调用过程,我们要修改 driver 和 url 两个参数,如清单 10所示。


清单 10
				
 String driver = "nl.griffelservices.proxy.jdbc.oracle.FileStubTracerDriver"; 
 String url = "jdbc:stub:output/mergerfile.xml";

通过对这两个参数的修改,JdbcProxy 将调用我们重写的方法,不需要通过 HTTP 服务器而是直接从整合文件中获取程序所需的响应结果。此处的改动包括:在 nl.griffelservices.proxy.stub 包下增加了 FileClient、FileStub、FileStubHandler、FileStubTracerMerger 四个类,在 nl.griffelservices.proxy.jdbc.oracle 包下增加了 FileStubTracerDriver 类,在 nl.griffelservices.proxy.jdbc.util 包下增加了 FileCombinedHandler 类。我们主要修改了 FileCombinedHandler 类中的 DriverUrl 方法用于解析新的参数;修改了 FileClient 类中的 invoke 方法用于从整合文件中读取相应的 response 。读者可以从文章最后的下载区中下载修改后的代码。

此时数据库驱动是我们改写的提供的FileStubTracerDriver,URL 的结构如下:

jdbc:stub:<filepath>

其中:

  • <filepath> 是整合文件的相对路径和文件名。

运行上述程序,我们可以在控制台得到与直接连接数据库时相同的返回结果,不必通过 HTTP 服务器 JdbcProxy 也可以进行 Java 应用的测试。

结语

JdbcProxy 可以让程序开发人员和测试人员脱离数据库的限制完成测试工作。如果测试工作需要多人共同完成,那么采用 HTTP 服务器的方式能够保证数据的一致性,更加便于小组成员协作;如果准备数据以及测试程序的工作由同一人完成,那么通过整合文件的方式则更加直接,能够提高程序运行的效率。

本文以一个实例介绍了 JdbcProxy 的基本功能,以及如何使用 JdbcProxy 进行数据的准备和测试。考虑到 JdbcProxy 只能通过 HTTP 服务器回放 JDBC 访问,本文还对 JdbcProxy 进行了改进,提供了直接通过整合的数据文件进行回放的方式。您可以根据实际情况选择合适的回放方式,参照这个实例对 JdbcProxy 进行修改,来测试您的 Java 应用程序,实现脱离数据库的测试工作。(责任编辑:A6)


时间:2008-11-14 15:31 来源:developerWorks 中国 作者:郑 溪龙 原文链接

好文,顶一下
(0)
0%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量