| |||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
---|---|---|---|---|---|---|---|---|---|
29 | 30 | 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 | 1 | 2 | |||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
數據庫測試
在創建企業級應用的時候,數據層的單元測試因為其復雜性往往被遺棄,Unitils大大降低了測試的復雜性,使得數據庫的測試變得容易并且易維護。已下介紹databasemodule和dbunitmodule進行數據庫的單元測試。
用dbUnit管理測試數據
數據庫的測試應該在單元測試數據庫上運行,單元測試數據庫給我們提供了一個完整的并有著很好細粒度控制的測試數據,DbUnitModule是在dbunit的基礎上進一步的為數據庫的測試提供數據集的支持。
加載測試數據集
讓我們以UserDAO中一個簡單的方法findByName(檢查姓氏和名字)為例子開始介紹。他的單元測試如下:
@DataSet
public class UserDAOTest extends UnitilsJUnit4 {
@Test
public void testFindByName() {
User result = userDao.findByName("doe", "john");
assertPropertyLenEquals("userName", "jdoe", result);
}
@Test
public void testFindByMinimalAge() {
List<User> result = userDao.findByMinimalAge(18);
assertPropertyLenEquals("firstName", Arrays.asList("jack"), result);
}
}
@DateSet 注解表示了測試需要尋找dbunit的數據集文件進行加載,如果沒有指明數據集的文件名,則Unitils自動在class文件的同目錄下加載文件名為 className.xml的數據集文件。(這種定義到class上面的數據集稱為class級別的數據集)
數據集 文件必須是dbunit的FlatXMLDataSet文件格式,其中包含了所要測試的數據。測試數據庫表中所有的內容將會被刪除,然后再插入數據集中的 數據。如果表不屬于數據集中的,哪么該表的數據將不會被刪除。你也可以明確的加入一個空的表元素,例如<MY_TABLE/>(可以達到刪除 測試數據庫表中內容的作用),如果要明確指定一個空的值,那么使用值[null]。
為UserDAOTest我們創建一個數據集,并放在UserDAOTest.class文件同目錄下。
<?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>
測試運行的時候,首先將刪除掉usergroup表和user表中的所有內容,然后將插入數據集中的內容。其中name為smith的firstname的值將會是null。
假設testFindByMinimalAge()方法將使用一個特殊的數據集而不是使用class級別的數據集,你可以定義一個UserDAOTest.testFindByMinimalAge.xml 數據集文件并放在測試類的class文件同目錄下。
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<user userName="jack" age="18" />
<user userName="jim" age="17" />
</dataset>
這時,你在testFindByMinimalAge()方法使用@DataSet注解,他將覆蓋class級的數據集
public class UserDAOTest extends UnitilsJUnit4 {
@Test
@DataSet("UserDAOTest.testFindByMinimalAge.xml")
public void testFindByMinimalAge() {
List<User> result = userDao.findByMinimalAge(18);
assertPropertyLenEquals("firstName", Arrays.asList("jack"), result);
}
}
不要過多的使用method級的數據集,因為過多的數據集文件意味著你要花大量的時間去維護,你優先考慮的是使用class級的數據集。
配置數據集加載策略
缺省情況下數據集被寫入數據庫采用的是clean insert策略。這就意味著數據在被寫入數據庫的時候是會先刪除數據集中有使用的表的數據,然后在將數據集中的數據寫入數據庫。加載策略是可配額制的,我們通過修改DbUnitModule.DataSet.loadStrategy.default 的屬性值來改變加載策略。假設我們在unitils.properties屬性文件中加入以下內容:
DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.InsertLoadStrategy
這時加載策略就由clean insert變成了insert,數據已經存在表中將不會被刪除,測試數據只是進行插入操作。
加載策略也可以使用@DataSet的注解屬性對單獨的一些測試進行配置:
@DataSet(loadStrategy = InsertLoadStrategy.class)
對于那些樹形DbUnit的人來說,配置加載策略實際上就是使用不同的DatabaseOperation,以下是默認支持的加載策略方式:
l CleanInsertLoadStrategy: 先刪除dateSet中有關表的數據,然后再插入數據。
l InsertLoadStrategy: 只插入數據。
l RefreshLoadStrategy: 有同樣key的數據更新,沒有的插入。
l UpdateLoadStrategy: 有同樣key的數據更新,沒有的不做任何操作。
配置數據集工廠
在Unitils中數據集文件采用了multischema xml 格式,這是DbUnits的FlatXmlDataSet 格式的擴展。配置文件格式和文件的擴展可以采用DataSetFactory 。
雖然Unitils當前只支持一種數據格式,但是我們可以通過實現DataSetFactory來使用其他文件格式。當你想使用excel而不是xml格式的時候,可以通過unitils.property中的DbUnitModule.DataSet.factory.default 屬性和@DataSet 注解來創建一個DbUnit's XlsDataSet 實例。
驗證測試結果
有些時候我們想在測試時完畢后使用數據集來檢查數據庫中的內容,舉個例子當執行完畢一個存儲過程后你想檢查一下啊數據是否更新了沒有。
下面的例子表示的是禁用到一年內沒有使用過的帳戶
public class UserDAOTest extends UnitilsJUnit4 {
@Test @ExpectedDataSet
public void testInactivateOldAccounts() {
userDao.inactivateOldAccounts();
}
}
注意在test方法上增加了一個@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>
根據這個數據集,將會檢查是否有兩條和記錄集的值相同的記錄在數據庫中。而其他的記錄和表將不理會。
使用的是@DataSet 注解的話,文件名可以明確指出,如果文件名沒有明確指出來,那么文件名將匹配className .methodName -result.xml
使用少使用結果數據集,加入新的數據集意味著更多的維護。替代方式是在代碼中執行相同的檢查(如使用一個findactiveusers()方法)。
使用多模式的數據集
一個程序不單單只是連接一個數據庫shema。Unitils采用了擴展的數據集xml來定義多schemas下的數據。以下就是一個讀取數據到2個不同的schemas中的例子:
<?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>
在這個例子中我定義了兩個schemas,SCHEMA_A 和 SCHEMA_B。第一個schema,SCHEMA_A 被連接到默認的xml命名空間中,第二個schema,SCHEMA_B 被連接到命名空間b。如果表xml元素的前綴使用了命名空間b,那么該表就是schema SCHEMA_B 中的,如果沒有使用任何的命名空間那么該表將被認為是SCHEMA_A
中的。以上例子中測試數據定義了表SCHEMA_A.user 和SCHEMA_B.role。
如果在數據集中沒有配置一個默認的命名空間,那么將會采用在unitils.properties中的屬性database.schemaNames 的第一個值作為默認的
database.schemaNames=SCHEMA_A, SCHEMA_B
這個配置將SCHEMA_A 作為缺省的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>
連接測試數據庫
在以上所有的例子中,我們都有一件重要的事情沒有做:當我們進行測試的時候,怎樣連接數據庫并得到DataSource?
當測試套件的第一個測試數據庫的案例運行的時候,Unitils將會通過屬性文件創建一個DataSource 的實例來連接你單元測試時的數據庫,以后的測試中都將使用這個DataSource 實例。連接配置的詳細內容如下:
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 中去,而用戶名,密碼以及schema可以配置到unitils-local.properties 中去,這樣可以讓開發人員連接到自己的單元測試數據庫中進行測試而不會干預到其他的人。
在屬性或者setter方法前使用注解@TestDataSource ,將會將DataSource 實例注入到測試實例中去,如果你想加入一些代碼或者配置一下你的datasource,你可以做一個抽象類來實現該功能,所有的測試類都繼承該類。一個簡單的例子如下:
public abstract class BaseDAOTest extends UnitilsJUnit4 {
@TestDataSource
private DataSource dataSource;
@Before
public void initializeDao() {
BaseDAO dao = getDaoUnderTest();
dao.setDataSource(dataSource);
}
protected abstract BaseDAO getDaoUnderTest();
}
上面的例子采用了注解來取得一個datasource的引用,另外一種方式就是使用DatabaseUnitils.getDataSource() 方法來取得datasource。
事務
出于不同的原因,我們的測試都是運行在一個事務中的,其中最重要的原因如下:
l 數據庫的很多action都是在事務正常提交后才做,如SELECT FOR UPDATE 和觸發器
l 許多項目在測試數據的時候都會填寫一些測試數據,每個測試運行都會修改或者更新了數據,當下一個測試運行的時候,都需要將數據回復到原有的狀態。
l 如果使用的是hibernate或者JPA的時候,都需要每個測試都運行在事務中,保證系統的正常工作。
缺省情況下,事務管理是disabled的,事務的默認行為我們可以通過屬性文件的配置加以改變:
DatabaseModule.Transactional.value.default=commit
采用這個設置,每個的測試都將執行commit,其他的屬性值還有rollback 和disabled
我們也可以通過在測試類上使用注解@Transactional 來改變默認的事務設置,如:
@Transactional(TransactionMode.ROLLBACK)
public class UserDaoTest extends UnitilsJUnit4 {
通過這種class上注解的事務管理,可以讓每個測試都確保回滾,@Transactional 注解還可以繼承的,因此我們可以將其放在父類中,而不必每個子類都進行聲明。
.........
如果你使用Unitils的spring支持(見使用spring進行測試)你如果配置了PlatformTransactionManager 的bean,那么unitils將會使用這個事務管理。
1、使用Dir: