乐者为王

Do one thing, and do it well.

DbUnit结合Spring进行SqlMap的单元测试

使用DbUnit,开发者可以控制测试数据库的状态。进行一个DAO单元测试之前,DbUnit为数据库准备好初始化数据;而在测试结束时,DbUnit会把数据库状态恢复到测试前的状态。下面的例子使用DbUnit为SqlMap编写单元测试。

SqlMap映射文件User.xml的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<select id="getUser" parameterClass="String" resultClass="User">
    SELECT username, password, first_name, last_name
    FROM user WHERE username = #username#
</select>

<delete id="removeUser" parameterClass="String">
    DELETE FROM user WHERE username = #username#
</delete>

<update id="updateUser" parameterClass="User">
    UPDATE user SET
        password = #password#,
        first_name = #firstName#,
        last_name = #lastName#
    WHERE username =#username#
</update>

<insert id="insertUser" parameterClass="User">
    INSERT INTO user (username, password, first_name, last_name)
    VALUES (#username#, #password#, #firstName#, #lastName#)
</insert>

配置文件sql-map-config.xml中的内容如下:

1
2
3
4
5
6
7
8
9
10
<sqlMapConfig>
    <settings lazyLoadingEnabled="true"
              cacheModelsEnabled="true"
              enhancementEnabled="true"
              maxSessions="64"
              maxTransactions="8"
              maxRequests="128" />

    <sqlMap resource="com/codemany/netlink/dao/impl/User.xml" />
</sqlMapConfig>

首先,要为单元测试准备测试数据。我们可以用DbUnit的Flat XML格式来准备测试数据集。下面的XML文件称为目标数据库的Seed File,它为测试准备了两条数据。其中元素名user对应数据库的表名,属性username,password,first_name和last_name是表user的列名,属性值就是记录值。

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<dataset>
    <user username="ford"
        password="ford"
        first_name="Henry"
        last_name="Ford" />

    <user username="twain"
        password="twain"
        first_name="Mark"
        last_name="twain" />
</dataset>

缺省情况下,DbUnit在单元测试开始之前执行CLEAN_INSERT操作,删除Seed File中所有表的数据,并导入Seed File的测试数据。我们可以通过覆盖getSetUpOperation()和getTearDownOperation()方法来控制单元测试前和测试后的数据库状态。一种高效的实施方案就是让getSetUpOperation()方法执行REFRESH操作,通过执行这个操作,我们可以用Seed File中的数据去更新目标数据库里的数据。接下来,就是getTearDownOperation(),让他执行一个NONE操作,也就是什么也不执行。

1
2
3
4
5
6
7
protected DatabaseOperation getSetUpOperation() throws Exception {
    return DatabaseOperation.REFRESH;
}

protected DatabaseOperation getTearDownOperation() throws Exception {
    return DatabaseOperation.NONE;
}

为了方便测试,我们为SqlMap的单元测试编写一个抽象的测试基类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public abstract class SqlMapTestCase extends DatabaseTestCase {
    protected SqlMapClient sqlMapClient = null;
    protected Properties props = new Properties();

    protected IDatabaseConnection getConnection() throws Exception {
        props.load(Resources.getResourceAsStream("properties/database.properties"));

        Class.forName(props.getProperty("driver"));
        Connection conn = DriverManager.getConnection(props.getProperty("url"),
                props.getProperty("username"), props.getProperty("password"));
        return new DatabaseConnection(conn);
    }

    protected IDataSet getDataSet() throws Exception {
        String resource = "com/codemany/netlink/dao/impl/dataset.xml";
        return new FlatXmlDataSet(Resources.getResourceAsStream(resource));
    }

    protected void setUp() throws Exception {
        super.setUp();

        // Build the SqlMapClient
        Reader reader = Resources.getResourceAsReader("sql-map-config.xml");
        sqlMapClient = SqlMapClientBuilder.buildSqlMapClient(reader);

        // Tell the SqlMapClient to use the given DataSource
        DataSource dataSource = getDataSource();
        TransactionConfig transactionConfig = getTransactionConfig(dataSource);
        // Apply the given TransactionConfig to the SqlMapClient
        applyTransactionConfig(sqlMapClient, transactionConfig);
    }

    protected void tearDown() throws Exception {
        super.tearDown();

        if (sqlMapClient != null) {
            DataSource ds = sqlMapClient.getDataSource();
            Connection conn = ds.getConnection();
            conn.close();
        }
    }

    private DataSource getDataSource() throws Exception {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(props.getProperty("driver"));
        dataSource.setUrl(props.getProperty("url"));
        dataSource.setUsername(props.getProperty("username"));
        dataSource.setPassword(props.getProperty("password"));
        return dataSource;
    }

    private TransactionConfig getTransactionConfig(DataSource dataSource) throws Exception {
        Properties transactionConfigProperties = new Properties();
        transactionConfigProperties.setProperty("SetAutoCommitAllowed", "false");

        TransactionConfig transactionConfig = (TransactionConfig)ExternalTransactionConfig.class.newInstance();
        transactionConfig.setDataSource(dataSource);
        transactionConfig.initialize(transactionConfigProperties);
        return transactionConfig;
    }

    private void applyTransactionConfig(SqlMapClient sqlMapClient, TransactionConfig transactionConfig) {
        if (!(sqlMapClient instanceof ExtendedSqlMapClient)) {
            throw new IllegalArgumentException("Cannot set TransactionConfig with DataSource"
                    + "for SqlMapClient if not of type ExtendedSqlMapClient: " + sqlMapClient);
        }
        ExtendedSqlMapClient extendedClient = (ExtendedSqlMapClient)sqlMapClient;
        transactionConfig.setMaximumConcurrentTransactions(extendedClient.getDelegate().getMaxTransactions());
        extendedClient.getDelegate().setTxManager(new TransactionManager(transactionConfig));
    }
}

然后为每个SqlMap映射文件编写一个测试类,继承上面的抽象类:

1
2
3
4
5
6
7
8
public class UserSqlMapTest extends SqlMapTestCase {

    public void testGetUser() throws Exception {
        User user = (User)sqlMapClient.queryForObject("getUser", "ford");
        assertNotNull(user);
        assertEquals("ford", user.getName());
    }
}

如此就可以进行单元测试了。

Comments