??? ??? 做 java 企業級開發時,我們通常采用三層架構。特別地,如果我們要做的系統的業務邏輯不是很復雜時,我們要處理的不過是 CRUD 操作,這時我們可能將 dao 層與 service 層合并為一層,盡管很多人會這樣做,但我仍傾向于將兩層分開;因為 service 與 dao 不是一一對應的,從復用及邏輯清晰的角度考慮,應該將它們分開。在三層架構下,對于 web 層, service 層, dao 層我們都該怎么測試?這里我將介紹基于 Spring , Hibernate 和 DbUnit 的情況下我的測試方法。由于使用了 Spring ,事務管理就不在 dao ,因此要單獨地測試 dao 可能要麻煩一些;另一方面, dao 中的操作大多是簡單的,也不是很值得測試。在使用了 Hibernate 和 Spring 的情況下,我們要測試的除了 HQL ,還有其配置文件,我覺得對數據持久化的測試最好定在 service 上。如果 service 業務邏輯復雜的話,與數據持久化無關的業務邏輯(應該寫在領域對象中)可以單獨測試 ,在保證與數據持久化無關的業務邏輯的正確性下,帶上 dao 操作做集成(單元)測試。
??? ??
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 有時本身就失掉了測試的意義。