没有比人更高的山

DbUnit中文教程——核心组件0

注:本文基本翻译自DbUnit的文档,翻译不好朋友见谅

本文主要介绍在使用DBUnit编写测试用例的时候会常用的几个接口和类
IDatabaseConnection:接口,DbUnit的数据库连接对象,封装了java.sql.Connection
IDataSet:接口,DbUnit的数据集对象,里面可以放多个数据表
DataBaseOperation:抽象类,数据库操作类
 
IDatabaseConnection有两个子类:
DatabaseConnection:封装了一个普通的JDBC连接
DatabaseDataSourceConnection:封装了一个JDBC数据源连接
 
IDataSet是DbUnit用来存储数据的一种数据结构
FlatXmlDataSet:最普遍使用的数据集,每个XML元素代表数据表中的一行数据,XML元素的名称是表明,属性名是列名
XmlDataSet
StreamingDataSet
DatabaseDataSet
QueryDataSet
DefaultDataSet
CompositeDateSet
FilteredDataSet
XlsDataSet
ReplacementDataSet
 
DatabaseOperation
DatabaseOperation.UPDATE:这个操作将从测试数据源中读取的数据集的内容更新到数据库中,注意这个操作正确执行的前提是测试数据表已经存在,如果不存在这个测试用例将会失败
DatabaseOperation.INSERT:这个操作把从测试数据源中读取的数据集的内容插入到数据库中,注意这个操作正确执行的前提是测试数据表不存在,这个操作将新建数据表。如果测试数据表已经存在这个测试用例将会失败。另外,在执行这个操作的时候要特别注意数据集中数据表的顺序,否则可能会因为违反外键约束而造成测试用例失败
DatabaseOperation.DELETE:这个操作会从数据库中删除数据,注意,这个操作只删除数据集中存在的数据行而不是整个数据表中的数据
DatabaseOperation.DELETE_ALL:这个操作删除数据表中的所有行,注意,这个操作也只影响数据集中声明了的数据表,数据集中没有涉及到的数据表中的数据不会删除
DatabaseOperation.TRUNCATE:这个操作将删除数据集中声明的数据表,如果数据中有些表并没有在预定义的数据集中提到,这个数据表将不会被影响。注意,这个操作是按照相反的顺序执行的
DatabaseOperation.REFRESH:顾名思义,这个操作将把预定义数据集中的数据同步到数据库中,也就是说这个操作将更新数据数中已有的数据、插入数据库中没有的数据。数据库中已有的、但是数据集中没有的行将不会被影响。我们用一个产品数据库的拷贝进行测试的时候可以使用这个操作将预定义数据同步到产品数据库中
DatabaseOperation.CLEAN_INSERT:删除所有的数据表中的数据,然后插入数据集中定义的数据,如果你想保证数据库是受控的,这个你会比较喜欢。
DatabaseOperation.NONE:不执行任何操作
CompositeOperation:将多个操作组合成一个,便以维护和重用
TransactionOperation:在一个事物内执行多个操作
IdentityInsertOperation:在使用MSSQL的时候,插入数据时IDENTITY列我们是没有办法控制的,用这个就可以控制了,只有在使用MSSQL的时候才会用得到
VN:F [1.7.5_995]
Rating: 10.0/10 (3 votes cast)
VN:F [1.7.5_995]
Rating: +3 (from 3 votes)

DbUnit中文教程——基本原理和简单开始0

DBUnit是JUnit的一个扩展,对于数据库驱动的项目而言(基本上所有的Web项目都是数据库驱动的),对于服务层的单元测试非常麻烦,因为不能保证每次测试时数据库都是同一个状态,所以开发者不敢写断言(assertEquals())。我个人也是因为这个原因所以对驱动测试开发总是敬而远之。

有了DBUnit,一切都变了,DBUnit的目的就是在每个单元测试运行之前将数据库初始化成一个预定义的状态,以保证单元测试时的断言不会因为数据库状态发生了变化而失败,同时可以解决前一个单元测试失败导致对数据库的操作未按照测试用例执行而影响后一个单元测试的问题。

DBUnit可以将数据库中的数据导出成XML数据集,也可以将XML数据集重新导入到数据库中,这也是DBUnit实现测试前初始化数据库状态的实现方法,而且,DBUnit还可以验证数据库中的模式和数据是不是和XML数据集中的模式和数据是否一样,也就是真正意义上的“数据库测试”,不过DBUnit貌似还不支持数据库存储过程、触发器、视图等高级功能的测试。DBUnit2.0增强了对于流模式下巨型XML数据集的支持。

相信信仰单元测试驱动开发或者有意向在开发中加入单元测试的开发者很容易理解上面的叙述,下面用一个示例演示DBUnit的功能。

假设我们开发中已经建立了一个数据库,里面只有一个数据表user:

create table user
(
id bigint auto_increament primary key,
username char(48) not null,
password char(48) not null,
name varchar(48) not null
);

其中有一行数据:
insert into user(id, username, password, name) values(1, 'admin', '123', 'Administrator');

第一步是根据测试数据建立XML数据集,DBUnit用下面的格式来表示一行数据:
<user id="1" username="admin" password="123" name="Administrator" />

一个XML数据集应该具有如下格式:

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <user id="1" username="admin1" password="123" name="Administrator1" />
    <user id="2" username="admin2" password="123" name="Administrator2" />
    <user id="3" username="admin3" password="123" name="Administrator3" />
</dataset>

 

一般,最好对每个需要数据库驱动的测试用例都建立一个XML数据集,一方面可以保证单元测试之间完全的数据独立,另一方面可以只设置测试需要的数据,尽量使XML数据集保持简单可控。

第二步就可以开始编码了,DBUnit提供了一个抽象类DatabaseTestCase,这个类继承了JUnit的TestCase类,其中定义了两个抽象方法: 
/**
 * 返回测试用的数据库连接对象
 */
protected abstract IDatabaseConnection getConnection() throws Exception;

/**
 * 返回测试用XML数据集对象
 */
protected abstract IDataSet getDataSet() throws Exception; 

getConnection()的返回值类型为IDatabaseConnection,对java.sql.Connection进行了简单的封装,可以简单的实现如下:
 protected IDatabaseConnection getConnection() throws Exception {
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/dbunitdemo", "root", "admin");
    return new MySqlConnection(conn, "dbunitdemo");//本人用的是MySQL
}

getDataSet()的返回值是IDataSet类型,即从XML数据集中读取数据和结构,实例化为IDataSet对象,可以简单实现如下:
protected IDataSet getDataSet() throws Exception {
    InputStream is = new FileInputStream("dbunitdemo-seed.xml");
    return new FlatXmlDataSet(is);
}

定义了以上两个方法后,DBUnit就可以工作了,但是不少人会就会想到一个问题,如果数据库中存在以前的数据,而我需要将这些数据删除,DBUnit会自动的帮我做这些事情么?事实上,很显然DBUnit会,其默认操作就是删除数据,同时将XML数据集中的数据插入到数据库中,不过DBUnit也允许我们通过覆盖DatabaseTestCase中的getSetUpOperation()方法来实现自己的动作,在DatabaseTestCase中默认实现是这样的:
protected DatabaseOperation getSetUpOperation() throws Exception
{
    return DatabaseOperation.CLEAN_INSERT;
}

DatabaseOperation是DBUnit定义的数据库操作对象,CLEAN_INSERT代表清空数据、插入XML数据集数据的操作。 

与SetUpOperation对象就有TearDownOperation,表示结束测试的时候做的事情,DatabaseTestCase的默认实现是什么都不做:
protected DatabaseOperation getTearDownOperation() throws Exception
{
    return DatabaseOperation.NONE;
}
同样,开发者可以根据自己的需求来改变默认操作。

基本原理就已经讲解完了,我写了一个例子程序,实现了一个领域模型类(User),一个Dao接口(IUserDao),定义了list\save\get\delete四个基本方法,一个用JDBC实现的Dao接口实现类(UserDaoImpl),建立一个UserDaoTest对JBDC版本的Dao实现类进行测试。

例子程序下载地址:dbunitdemo_jdbc_1.0.zip

另外这个工程使用maven进行build,如果你对maven还是不很了解,请查考《Maven权威指南》

下载工程后,在项目根目录下执行mvn sql:execute,将使用Maven Sql插件导入src/main/sql/dbunitdemo.sql中的数据库脚本到数据库中。

然后可以直接运行mvn test进行单元测试,测试报告会在命令行输出,同时在target\surefire-reports下也会生成DBUnit报告

VN:F [1.7.5_995]
Rating: 3.3/10 (3 votes cast)
VN:F [1.7.5_995]
Rating: 0 (from 2 votes)

Java中文大写数字转int型0

暂时不支持万以上级别,后面会加上这个。道理很简单,但我搜了一下google,找到的代码没有几个能直接用,所以分享下,直接贴代码了:

    public static int chnNumToIntConverter(String chnNum){
        HashMap<String, Integer> numbers = new HashMap<String, Integer>();
        numbers.put(”零”, 0);
        numbers.put(”一”, 1);
        numbers.put(”二”, 2);
        numbers.put(”三”, 3);
        numbers.put(”四”, 4);
        numbers.put(”五”, 5);
        numbers.put(”六”, 6);
        numbers.put(”七”, 7);
        numbers.put(”八”, 8);
        numbers.put(”九”, 9);
        HashMap<String, Integer> units = new HashMap<String, Integer>();
        units.put(”十”, 10);
        units.put(”百”, 100);
        units.put(”千”, 1000);
       
        int result = 0;
       
        //补全“十三”这种类型前的“一”
        if(chnNum.startsWith(”十”)){
            chnNum = “一” + chnNum;
        }
       
        int length = chnNum.length();
        for(int i=0; i<length; i++){
            String num = chnNum.substring(i, i+1);
            if(! num.equals(”零”)){
                if(i+2 > length){
                    result += numbers.get(num);
                }else{
                    String unit = chnNum.substring(i+1, i+2);
                    result += numbers.get(num) * units.get(unit);
                }
                i++;
            }
        }
       
        return result;
    }

VN:F [1.7.5_995]
Rating: 1.5/10 (2 votes cast)
VN:F [1.7.5_995]
Rating: 0 (from 0 votes)

Working copy path '*' does not exist in repository的解决方法0

开发中对一个working copy进行update操作的时候出现了标题所示的错误,因为在update之前对其进行了switch操作,因此猜想可能是因为这个缘故,上网查了一下,找到一篇文章《SVN merger errors: Working copy path ‘*’ does not exist in repo》,按照其描述的方法并不能解决我的问题,但是让我对.svn文件夹下的这个entry文件产生了兴趣,仔细的看了一下,发现这个文件里面有一行是写的版本号,紧接着下一行就是文件夹路径,但是这个文件夹路径是错误的。

    对其进行了改正后,重新update,正常了,看来是svn的一个bug吧。

VN:F [1.7.5_995]
Rating: 0.0/10 (0 votes cast)
VN:F [1.7.5_995]
Rating: 0 (from 0 votes)

Lucene中的TooManyClause异常0

为什么会产生这个异常

使用Lucene检索过程中如果用到RangeQuery,PrefixQuery,WildcardQuery,FuzzyQuery这四种Query,可能会产生TooManyClauses异常。为什么会产生这个异常呢?举例说明:

以RangeQuery为例,如果日期范围为19990101到20091231,在索引文件中有19990102,19990103等等这些日期词组,那么RangeQuery会被扩展成“19990102 OR 19990103”,成了2个子句。可以想象,如果索引文件里面在这个时间段内的日期有很多,那么就会产生很多子句。

PrefixQuery等也是同样的道理,如查询词为“法*”,如果索引文件中有“法律”、“法场”、“法医”、“法典”等等,这个Query就会被扩展成“法律 OR 法场 OR 法医 OR 法典”,或许会更多更多。

而为了节省内存,Lucene默认将子句数目限制为1024,如果超出限制,就会抛出TooManyClauses异常。

怎么解决这个问题呢,Lucene提供了三种方法:

【1】使用filter替代Query,当然这是以牺牲查询速度为代价的,不过可以通过缓存的方式缓解这个问题。仍然以前面RangeQuery为例,可以使用RangeFilter来替代RangeQuery,如下:
之前的代码:

1
2
3
4
5
BooleanQuery simpleQuery = new BooleanQuery();
Term dateLower = new Term("publishDate", startYear + "0101");
Term dateUpper = new Term("publishDate", endYear + "1231");
RangeQuery dateQuery = new RangeQuery(dateLower, dateUpper, true);
simpleQuery.add(dateQuery, Occur.MUST);

之后的代码:

1
2
3
BooleanQuery simpleQuery = new BooleanQuery();
RangeFilter dateFilter = new RangeFilter("publishDate", startYear + "0101", endYear + "1231", true, true);
FilteredQuery filteredQuery = new FilteredQuery(simpleQuery, dateFilter);

【2】通过BooleanQuery.setMaxClauseCount(10240)来限制数目。这样会加大内存的消耗。使用BooleanQuery.setMaxClauseCount(Integer.MAX_VALUE),可以完全去掉这个限制。

【3】对于范围查询,可以尽可能的降低精度,比如如果查询不需要精确到月份与日期,只需要精确到年,据说可以使用DateTools这个类可以很简单解决时间转化问题,本人并没有尝试。

VN:F [1.7.5_995]
Rating: 4.5/10 (2 votes cast)
VN:F [1.7.5_995]
Rating: +1 (from 1 vote)


Switch to our mobile site