Unitils 使用指南[翻译]-数据库测试

 

1.1.1       数据库测试

数据库层的单元测试对构建企业应用来说是比较有价值的,但是由于过于复杂我们不得不放弃他。Unitils降低了数据库测试的复杂度,让数据库测试简单而又容易维护,下面的章节描述DatabaseModule andDbUnitModule 怎么对你的数据库测试提供支持。

1.1.1.1     使用Dbunit维护测试数据

数据库测试应该使用单元测试数据库,这样你可以完全的精细的控制你使用到的测试数据。DbUnitModule 是基于DBunit构建的,可以提供对测试数据集的支持。

1.1.1.1.1    装载测试数据集

让我们看一个例子,UserDao有一个简单的方法findByName,用来通过用户的first last name来取回用户,常用的单元测试如下:

@DataSet
public class UserDAOTest extends UnitilsJUnit4 {
 
    @Test
    public void testFindByName() {
        User result = userDao.findByName("doe", "john");
        assertPropertyLenientEquals("userName", "jdoe", result);
    }
 
    @Test
    public void testFindByMinimalAge() {
        List<User> result = userDao.findByMinimalAge(18);        
        assertPropertyLenientEquals("firstName", Arrays.asList("jack"), result);
    }
}
 

@DataSet注解是通知Unitils查找测试需要加载的Dbunit数据文件。如果没有指定文件名,Unitils会在当前文件夹自动查找和测试类文件名相同的数据集文件如:className.xml.

数据集文件应该使用Dbunit FlatXMLDataSet 格式,并且应该包含所有测测试数据。所有表的内容首先被清空,然后所有的测试数据被插入。不在数据文件中的表,是不会被清空内容的。你如果需要清空特定的表你可以在文件中加入一个表的空标签,如:<MY_TABLE />,对于插入null值,你也可以使用类似的方法。

UserDaoTest你需要建立一个数据集文件名称为:UserDaoTest.xml 并且把它放到UserDaoTest 类文件所在的目录。

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
 
    <usergroup name="admin" />  
    <user userName="jdoe"  name="doe"   firstname="john"   userGroup="admin" />
    
    <usergroup name="sales" />    
    <user userName="smith" name="smith" userGroup="sales" />
    
</dataset>
 

这会清空user表和usergroup表,并且插入新的记录,用户名为smith的用户的first name会被设置null值。

支持testFindByMinimalAge()方法需要特殊的数据集而不是类级别的数据集。那么你需要建立一个文件:UserDAOTest.testFindByMinimalAge.xml  并且放在和测试类相同的文件夹即可

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
    <user userName="jack" age="18" />
    <user userName="jim"  age="17" />
</dataset>
 

你可以用这个数据集文件通过给这个方法上标注@DataSet注解,来覆盖类的数据集文件。

public class UserDAOTest extends UnitilsJUnit4 {
 
    @Test
    @DataSet("UserDAOTest.testFindByMinimalAge.xml")
    public void testFindByMinimalAge() {
        List<User> result = userDao.findByMinimalAge(18);        
        assertPropertyLenientEquals("firstName", Arrays.asList("jack"), result);
    }
}
 

方法级别的数据集文件不应该被过度使用,因为过多的数据文件意味这更多的维护工作,你应该尽量减少数据在类级别的数据集,多数情况下比较小的数据集就可以给多个单元测试公用。但是如果公用数据导致了数据量的增大和杂乱,那么用方法级别的数据集,或者细分单元测试类,每个类用自己的数据集。

给一个类或者其父类通过@DataSet设置的数据集对类里的每个测试方法都有效。如果一个数据集只被几个测试方法使用,那么你最好不要把他们放在类级别的注解里,而应该在相应的测试方法上加上注解。如果你的数据集文件没有像我们前面描述的那样命名,你也可以自己命名,当然只需要在@dataset注解中标明即可,你也可以使用多个数据集文件,如下示例:

 

@DataSet({"UserDAOTest_general.xml", "ConfigSettings.xml"})
public class UserDAOTest extends UnitilsJUnit4 {
 
    @Test
    public void testFindByName() {
        User result = userDao.findByName("doe", "john");
        assertPropertyLenientEquals("userName", "jdoe", result);
    }
 
    @Test 
    @DataSet("UserDAOTest_ages.xml")
    public void testFindByMinimalAge() {
        List<User> result = userDao.findByMinimalAge(18);
        assertPropertyLenientEquals("firstName", Arrays.asList("jack"), result);
    }
}
 
1.1.1.1.2 数据集加载策略设置

默认情况下,数据集加载是,采用先清除后插入的策略。那就意味着所有涉及的表中数据会被删除,然后再插入测试数据。这个动作时可以被设置的,你可以通过如下配置来编辑DbUnitModule.DataSet.loadStrategy.default
如果我们在Unitils.properties文件中这么修改:

DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.InsertLoadStrategy
 

这样设置的话,就不会先删除现有数据了,而只是插入数据。

加载策略也可以对特定的测试类来设置,需要在@DataSet注解中这么标注:

@DataSet(loadStrategy = InsertLoadStrategy.class)
 

如果你熟悉Dbunit,配置加载策略类似于使用不同的数据库,下面是默认支持的加载策略:

  • CleanInsertLoadStrategy: 先清除后插入策略
  • InsertLoadStrategy: 仅仅插入数据
  • RefreshLoadStrategy: 刷新数据库内容,如果数据库已经有了会使用数据集的数据更新它,如果数据库没有,就会把数据集的输入插入。数据库里有但是数据集中没有的数据不会受到影响。
  • UpdateLoadStrategy: 数据库存在的数据会被更新,但是如果数据集中有但是数据库中没有的话不会有数据变动。
1.1.1.1.3 配置数据集工厂

Unitils的数据集文件使用multischema xml  格式,这是DbUnits FlatXmlDataSet 格式的一个扩展版本。数据集工厂来管理文件格式的配置和文件扩展。

尽管Unitils目前只支持一种数据集格式,但是通过自定义的数据集工厂的实现是可以支持不同的文件格式的。
你可以通过Unitils.propertis文件的DbUnitModule.DataSet.factory.default   属性配置或者在@DataSet注解中标明。例如你可以建立一个DbUnitXlsDataSet通过实现DataSetFactory来使用Excel文件作为数据集文件。

1.1.1.1.4 验证测试结果

在测试运行后,有时候使用数据集的数据来对比数据库的内容是比较有用的。例如当你想检查大量数据更新或者存储过程执行的结果。

下面的例子测试一个方法,这个方法禁用所有一整年没有活动的用户的帐号:

public class UserDAOTest extends UnitilsJUnit4 {
 
    @Test @ExpectedDataSet
    public void testInactivateOldAccounts() {
        userDao.inactivateOldAccounts();
    }
}
 

注意我们在这个方法上增加了@ExpectedDataSet 注解。这会让Unitils寻找数据集文件UserDAOTest.testInactivateOldAccounts-result.xml并且比较数据库内容和数据集的数据。

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
    <user userName="jack" active="true" />
    <user userName="jim"  active="false" />
</dataset>
 

对这个数据集,Unitils会检查是否有两个不同的用户记录在用户表。其他的记录或其他表不会涉及。
使用@DataSet注解,文件名是可以被指定的,如果名字没有指定,那么会查找类似格式文件名的文件: className.methodName-result.xml
结果数据集应该尽量小。数据多意味着维护量变大。还有,最好尽量执行相同的检查在测试代码里。

 

1.1.1.1.5 多数据库测试

一些应用使用了多个数据库。为了实现这个功能,Unitils的数据集XML文件中可以对多数据库进行支持。下面的例子展现了怎么为两个数据库的数据表装载数据。

<?xml version='1.0' encoding='UTF-8'?>
<dataset xmlns="SCHEMA_A" xmlns:b="SCHEMA_B">
    <user id="1" userName="jack" />    
    <b:role id="1" roleName="admin" />
</dataset>
 

这个例子我们定义了两个方案,ABschema-A和默认的XML命名空间关联,schema B关联命名空间b.如果一个表的描述中有前缀指明哪个命名空间,那么就会用指定的,否则默认schema A

如果没有默认的命名空间,那么系统会默认在database.schemaNames属性中第一个schema。所以系统是支持你如下定义的。

database.schemaNames=SCHEMA_A, SCHEMA_B

这样系统默认schema-A 为默认的schema,那么你就可以不用再声明默认的schema了。

<?xml version='1.0' encoding='UTF-8'?>
<dataset xmlns:b="SCHEMA_B">
    <user id="1" userName="jack" />    
    <b:role id="1" roleName="admin" />
</dataset>
 
1.1.1.1.6 连接测试数据库

上面的例子我们没有提到一件重要的事情,连接测试数据库的数据源在哪里,并且怎么让我们的UseDao使用这个数据源?

当你的第一个数据库测试在测试组件中运行的时候,Unitils会使用配置的属性建立一个数据源实例,去连接测试数据库。后面的数据库测试会仍会使用这个数据源,连接的详细属性描述如下:

database.driverClassName=oracle.jdbc.driver.OracleDriver
database.url=jdbc:oracle:thin:@yourmachine:1521:YOUR_DB
database.userName=john
database.password=secret
database.schemaNames=test_john
 

通常驱动和url配置放在Unitils.properties文件里作为项目共用,用户密码可以放在Unitils-local.properties作为每个开发者自己的设置。这样可以让每个开发者使用自己的测试数据,防止互相干扰。

当一个测试开始执行的时候,数据源示例会被注入到测试实例中,如果一个属性或者setter方法用注解@TestDataSource指定了,那么系统会使用你指定的数据源。你仍需要提供一些项目特定的代码来让你的代码使用这个数据源。通常这些配置在项目父类中被执行一次,简单的示例如下:

public abstract class BaseDAOTest extends UnitilsJUnit4 {
 
    @TestDataSource
    private DataSource dataSource;
    
    @Before    
    public void initializeDao() {
        BaseDAO dao = getDaoUnderTest();
        dao.setDataSource(dataSource);
    }
    
    protected abstract BaseDAO getDaoUnderTest();
}
 

The above example uses annotations to get a reference to the datasource. Another way of making your code use the Unitils DataSource is by callingDatabaseUnitils.getDataSource().

上面的例子使用注解获取一个指定的数据源。另一个使用UnitilsDataSource的方法是使用DatabaseUnitils.getDataSource().

1.1.1.1.7 事务处理

很多情况下我们我们要以事务的方式存取数据,例如:

  • 数据库动作仅在事务执行的时候存在,如使用 SELECT FOR UPDATE 或者提交的时候有触发器行为
  • 许多项目运行测试前,需要先准备一些general-purpose数据,每个测试前数据会有插入或修改。为了确保数据库在每个测试前都有一个已知的明确的状态,那么在每个测试前事务要开始,执行完以后事务需要回滚。
  • 当我们使用hibernate或者JPA,他可能要求你使用事务为每个测试,来保证系统的正常运行。

By default every test is executed in a transaction, which is committed at the end of the test.

默认状况下每个以事务方式执行的测试,完成后数据会被提交。

默认的动作可以通过配置来修改,如:

DatabaseModule.Transactional.value.default=disabled
 

可选的值还有: commit, rollback and disabled.

事务行为可以在测试类中被修改,会用到 @Transactional.注解如:

@Transactional(TransactionMode.ROLLBACK)
public class UserDaoTest extends UnitilsJUnit4 {
 

这样这个测试类的每个测试执行后数据将回滚。@Transactional 注解是可以被继承的。如果有必要你可以在你测试类的父类中使用。

其实,Unitils依赖Spring的事务管理,但这不意味着你必须使用Spring在你的应用代码里。实际上对用户来说那是透明的。

如果你继承了UnitilsSpring,并且你已经配置了bean的类型PlatformTransactionManager Spring配置里。Unitils会使用这个事务管理器。

 

你可能感兴趣的:(spring,oracle,xml,单元测试,配置管理)