??? ??? 做
java
企業級開發時,我們通常采用三層架構。特別地,如果我們要做的系統的業務邏輯不是很復雜時,我們要處理的不過是
CRUD
操作,這時我們可能將
dao
層與
service
層合并為一層,盡管很多人會這樣做,但我仍傾向于將兩層分開;因為
service
與
dao
不是一一對應的,從復用及邏輯清晰的角度考慮,應該將它們分開。在三層架構下,對于
web
層,
service
層,
dao
層我們都該怎么測試?這里我將介紹基于
Spring
,
Hibernate
和
DbUnit
的情況下我的測試方法。由于使用了
Spring
,事務管理就不在
dao
,因此要單獨地測試
dao
可能要麻煩一些;另一方面,
dao
中的操作大多是簡單的,也不是很值得測試。在使用了
Hibernate
和
Spring
的情況下,我們要測試的除了
HQL
,還有其配置文件,我覺得對數據持久化的測試最好定在
service
上。如果
service
業務邏輯復雜的話,與數據持久化無關的業務邏輯(應該寫在領域對象中)可以單獨測試
,在保證與數據持久化無關的業務邏輯的正確性下,帶上
dao
操作做集成(單元)測試。
??? ??
在使用
Spring
做測試時,推薦測試類繼承自
Spring
的
AbstractTransactionalDataSourceSpringContextTests
,這也是官方推薦的做法。
AbstractTransactionalDataSourceSpringContextTests
對測試方法提供了事務管理,同時它的依賴注入特性也方便測試類注入被測試類(但我并不認為它的事務管理是很需要的)。承接于上一篇文章中的例子
,下面給出測試類
AccountServiceTest。
package
?hibernatesample.service;
import
?hibernatesample.domain.Account;
import
?java.io.File;
import
?java.io.InputStream;
import
?java.util.List;
import
?org.dbunit.IDatabaseTester;
import
?org.dbunit.JdbcDatabaseTester;
import
?org.dbunit.dataset.IDataSet;
import
?org.dbunit.dataset.xml.FlatXmlDataSet;
import
?org.springframework.test.AbstractDependencyInjectionSpringContextTests;
import
?org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
public
?
class
?AccountServiceTest?
extends
?AbstractTransactionalDataSourceSpringContextTests?{
????
private
?AccountService?accountService;
????
private
?IDatabaseTester?databaseTester;
????
public
?AccountService?getAccountService()?{
????????
return
?accountService;
????}
????
????
public
?AccountServiceTest()
throws
?Exception{
????
????}
????
public
?
void
?setAccountService(AccountService?accountService)?{
????????
this
.accountService?
=
?accountService;
????}
????@Override
????
protected
?
void
?onSetUp()?
throws
?Exception?{
????????databaseTester?
=
?
new
?JdbcDatabaseTester(
"
com.mysql.jdbc.Driver
"
,
????????????????
"
jdbc:mysql://localhost/HibernateSample
"
,?
"
root
"
,?
"
0102
"
);
?????IDataSet?dataSet?
=
?getDataSet();
?????databaseTester.setDataSet(?dataSet?);
?????databaseTester.onSetup();
????}
????@Override
????
protected
?
void
?onTearDown()?
throws
?Exception?{
????????databaseTester.onTearDown();
????}
????
protected
?IDataSet?getDataSet()?
throws
?Exception?{
????????String?path?
=
?
"
hibernatesample
"
+
File.separator
+
"
dao
"
+
File.separator
+
"
dataset
"
+
File.separator
+
"
Account.xml
"
;
????????InputStream?in?
=
?
this
.getClass().getClassLoader().getResourceAsStream(path);
????????
return
?
new
?FlatXmlDataSet(in);
????}
????
????@Override
????
protected
?String[]?getConfigLocations()?{
????????
????????
return
?
new
?String[]{
"
classpath:service-applicationContext.xml
"
};
????}
????
????
public
?
void
?testInsert()?{
????????Account?a?
=
?
new
?Account();
????????a.setName(
"
aa
"
);
????????accountService.insertAccount(a);
????????List
<
Account
>
?l?
=
?accountService.findAllAccount();
????????assertEquals(
3
,?l.size());
????????Account?b?
=
?l.get(
2
);
????????assertEquals(
"
aa
"
,?b.getName());
????}
????
public
?
void
?testFindAll()?{
????????List
<
Account
>
?l?
=
?accountService.findAllAccount();
????????assertEquals(
2
,?l.size());
????????Account?a?
=
?l.get(
0
);
????????assertEquals(
new
?Long(
1
),?a.getId());
????????assertEquals(
"
kafka
"
,?a.getName());
????????Account?b?
=
?l.get(
1
);
????????assertEquals(
new
?Long(
2
),?b.getId());
????????assertEquals(
"
0102
"
,?b.getName());
????}
}
??? ??? 由于
AccountServiceTest
繼承了
AbstractTransactionalDataSourceSpringContextTests
,因此使用
D
b
Unit
時就不能繼承
DBTestCase
,這里采用的方法就是在
onSetUp()
中實現數據集的裝入。由于
AbstractTransactionalDataSourceSpringContextTests
最第
N
個頂層的父類繼承于
Junit
,所以
onSetUp()
將在
SetUp()
中被調用,默認的
onSetUp()
實現為空;
onTearDown()
的原理和
onSetUp()
相似。由于
AbstractTransactionalDataSourceSpringContextTests
具有依賴注入的特性,所以被測試對象
accountService
可以通過
setter
方法獲得;當然,我們也可以在構造函數中通過
Bean
工廠獲得
accountService
。
protected
String[] getConfigLocations()
返回的數組為
Spring
Beans
配置文件的位置。
??? ??? 對于與測試相關的其他類,這里就不給出了。通過使用
DbUnit
,我覺得測試持久化操作時方便了許多。不爽的地方在于,在使用了
Spring
,
Hibernate
的情況下,測試一個方法有些耗時,這自然不單單是
DbUnit
的問題。但如果我將
dao
和
service
的測試放在一起來做,這種耗時也只能忍著。再說一下
web
層的測試,我當前使用的
web
框架是
Webwork
,在測試
Action
的時候,對于
Web
請求之類使用
Mock
,對于
Service
的調用有時
Mock
有時真實調用。
Mock
的好處是耗時能少一些,但寫
Mock
代碼也很繁瑣,而對
service
的
Mock
有時本身就失掉了測試的意義。