DAO test
至此,一個(gè)基于MVC的基本Android應(yīng)用程序已經(jīng)初步形成了。
下面我們來(lái)實(shí)現(xiàn)一個(gè)具有TabHost的布局的典型Android應(yīng)用,由于我們基本上可以不考慮Android 4.x以前的版本,因此我對(duì)TabHost布局的實(shí)現(xiàn)將采用Fragment來(lái)實(shí)現(xiàn),而不是采用舊的ActivityGroup來(lái)實(shí)現(xiàn)。
同時(shí),我們希望我們的應(yīng)用程序可以適用于不同的項(xiàng)目,因此需要TabHost上的圖片及文字可以非常方便的進(jìn)行更換。我們采用下部有5個(gè)選項(xiàng)的布局,其中中間的選項(xiàng)可以突出顯示,選中某個(gè)選項(xiàng),目前僅顯示對(duì)應(yīng)Fragmentation的名字。
好了,需求基本說(shuō)清楚了,下面我們就開(kāi)始一步步實(shí)現(xiàn)吧。
首先是基于Fragment的TabHost布局實(shí)現(xiàn),原理很簡(jiǎn)單,在MainActivity的布局文件里添加如下代碼即可:
<?xml version="1.0" encoding="utf-8"?> <TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost" android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <!-- 實(shí)現(xiàn)Tab標(biāo)簽的居底主要是通過(guò)設(shè)置屬性 android:layout_weight="1" --> <!-- 還要注意FrameLayout標(biāo)簽的位置,要寫(xiě)在TabWidget標(biāo)簽的前面 --> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_gravity="center_horizontal" android:layout_weight="1"> <fragment android:id="@+id/j_dynamicFragment" android:name="com.bjcic.wkj.gui.DynamicFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> <fragment android:id="@+id/j_findFragment" android:name="com.bjcic.wkj.gui.FindFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> <fragment android:id="@+id/j_shareFragment" android:name="com.bjcic.wkj.gui.ShareFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> <fragment android:id="@+id/j_snsFragment" android:name="com.bjcic.wkj.gui.SnsFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> <fragment android:id="@+id/j_moreFragment" android:name="com.bjcic.wkj.gui.MoreFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout> <TabWidget android:id="@android:id/tabs" android:layout_width="fill_parent" android:layout_height="60dip" android:layout_gravity="center_horizontal" android:layout_marginLeft="-2dp" android:layout_marginRight="-2dp" android:background="@null" /> </LinearLayout> </TabHost> |
好的單元測(cè)試應(yīng)該是原子性的,獨(dú)立的,不應(yīng)依賴(lài)其他測(cè)試和上下文,但是要測(cè)試數(shù)據(jù)讀寫(xiě)是否正確,就必須涉及初始數(shù)據(jù)的加載,數(shù)據(jù)修改的還原等操作。對(duì)于初始數(shù)據(jù)的加載,手動(dòng)輸入很麻煩,一個(gè)解決方案就是使用Dbunit,從Xml文件甚至Excel中加載初始數(shù)據(jù)到數(shù)據(jù)庫(kù),是數(shù)據(jù)庫(kù)的值達(dá)到一個(gè)已知狀態(tài)。同時(shí)還可以使用Dbunit,對(duì)數(shù)據(jù)庫(kù)的結(jié)果狀態(tài)進(jìn)行判斷,保證和期望的一致。數(shù)據(jù)修改的還原,可以依賴(lài)Spring TransactionalTests,在測(cè)試完成后回滾數(shù)據(jù)庫(kù)。
Dbunit還可以對(duì)數(shù)據(jù)的現(xiàn)有數(shù)據(jù)進(jìn)行備份,還原,清空現(xiàn)有數(shù)據(jù),一個(gè)好的測(cè)試實(shí)踐是每一個(gè)開(kāi)發(fā)人員一個(gè)測(cè)試數(shù)據(jù)庫(kù),進(jìn)而對(duì)數(shù)據(jù)庫(kù)的數(shù)據(jù)狀態(tài)有更好的控制,但現(xiàn)實(shí)可能會(huì)是共享同一個(gè)測(cè)試庫(kù),所以這種情況下,測(cè)試的編寫(xiě)必須多做一些考慮。
待測(cè)試的類(lèi):
package com.test.dbunit.dao.impl; import java.sql.ResultSet; import java.sql.SQLException; import org.springframework.jdbc.core.RowMapper; import com.test.dbunit.dao.UserDao; import com.test.dbunit.entity.User; public class DefaultUserDao extends BaseDao implements UserDao { private static String QUERY_BY_NICK = "select * from user where user.nick = ?"; private static String REMOVE_USER = "delete from user where user.nick = ?"; private static String INSERT_USER = "insert into user(nick,password) values(?, ?)"; private static String UPDATE_USER = "update user set user.password = ? where user.nick = ?"; @Override public User getUserByNick(String nick) { return (User) getJdbcTemplate().queryForObject(QUERY_BY_NICK,new Object[]{nick}, new RowMapper(){ @Override public Object mapRow(ResultSet rs, int index) throws SQLException { User user = new User(); user.setNick(rs.getString("nick")); user.setPassword(rs.getString("password")); return user; } }); } @Override public void remove(String nick) { getJdbcTemplate().update(REMOVE_USER, new Object[]{nick}); } @Override public void save(User user) { getJdbcTemplate().update(INSERT_USER, new Object[]{user.getNick(), user.getPassword()}); } @Override public void update(User user) { getJdbcTemplate().update(UPDATE_USER, new Object[]{user.getPassword(), user.getNick()}); } } |
單元測(cè)試:
需要注意的地方就是,DataSourceUtils.getConnection(datasource) , 通過(guò)這種方式獲得數(shù)據(jù)庫(kù)連接初始化Dbunit,能夠保證Dbunit使用的數(shù)據(jù)連接和當(dāng)前事務(wù)的數(shù)據(jù)庫(kù)連接相同,保證能夠在參與到事務(wù)中。Spring的TransactionManager會(huì)在開(kāi)始事務(wù)時(shí)把當(dāng)前連接保存到ThreadLocal中,DataSourceUtils.getConnection方法,首先從ThreadLocal中獲取連接。
user001.xml
Xml代碼
<?xml version="1.0" encoding="UTF-8"?> <dataset> <user nick="user001" password="password001" /> </dataset> |
使用dbunit,可以通過(guò)xml文件定義數(shù)據(jù)集,也可以使用其他方式定義,比如Excel,編程方式。
Dbunit的主要構(gòu)件
IDatabaseConnection
數(shù)據(jù)庫(kù)鏈接。實(shí)現(xiàn)類(lèi)有DatabaseConnection 和DatabaseDataSourceConnection ,執(zhí)行數(shù)據(jù)庫(kù)操作時(shí)需要一個(gè)連接。
IDataSet
數(shù)據(jù)集,數(shù)據(jù)集可以從Xml文件Excel等外部文件獲取,也可以從數(shù)據(jù)庫(kù)查詢(xún),或者編程方式構(gòu)件,數(shù)據(jù)集可以作為初始數(shù)據(jù)插入到數(shù)據(jù)庫(kù),也可以作為斷言的依據(jù)。另外還有IDatatable等輔助類(lèi)。
比如在updateUser測(cè)試中,使用了QueryDataSet,從數(shù)據(jù)庫(kù)中構(gòu)建一個(gè)Dataset,再通過(guò)FlatXmlDataSet從Xml文件中構(gòu)建一個(gè)Dataset,斷言這兩個(gè)Dataset相同。
QueryDataSet actual = new QueryDataSet(conn); actual.addTable("user", "select * from user where user.nick = 'user001'"); IDataSet expected = new FlatXmlDataSet(new ClassPathResource( "com/taobao/dbunit/dao/user001_updated.xml").getFile()); Assertion.assertEquals(expected, actual); DatabaseOperation |
通過(guò)定義的靜態(tài)字段可以獲取一組代表一個(gè)數(shù)據(jù)操作的子類(lèi)對(duì)象,比如DatabaseOperation .INSERT,返回 InsertOperation,通過(guò)執(zhí)行execute方法把數(shù)據(jù)集插入到數(shù)據(jù)庫(kù)。例如:
IDataSet origen = new FlatXmlDataSet(new ClassPathResource(
"com/taobao/dbunit/dao/user001.xml").getFile());
DatabaseOperation.INSERT.execute(conn, origen);
從Xml文件中構(gòu)建DataSet,使用Insert插入到數(shù)據(jù)庫(kù),初始化測(cè)試數(shù)據(jù)。
Assertion
唯一的方法,assertEqual,斷言?xún)蓚€(gè)數(shù)據(jù)集或數(shù)據(jù)表相同。
更多關(guān)于Dbunit的組件的介紹:http://www.dbunit.org/components.html
PS:使用Oracle的時(shí)候,初始化DatabaseConnection需要傳入scheme。new DatabaseConnection(conn,SCHEMA_NAME ) ,SCHMEA_NAME需要大寫(xiě)。
附件提供所有代碼下載
package com.taobao.dbunit.dao; import java.sql.SQLException; import javax.sql.DataSource; import org.dbunit.Assertion; import org.dbunit.database.DatabaseConnection; import org.dbunit.database.IDatabaseConnection; import org.dbunit.dataset.DataSetException; import org.dbunit.dataset.DefaultDataSet; import org.dbunit.dataset.DefaultTable; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.xml.FlatXmlDataSet; import org.dbunit.operation.DatabaseOperation; import org.junit.Assert; import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.transaction.TransactionConfiguration; @ContextConfiguration(locations = { "classpath:testApplicationContext.xml" }) @TransactionConfiguration(defaultRollback = true) public class BaseDaoTest extends AbstractTransactionalJUnit4SpringContextTests { @Autowired private DataSource dataSource; private IDatabaseConnection conn; @Before public void initDbunit() throws Exception { conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource)); } /** * 清空f(shuō)ile中包含的表中的數(shù)據(jù),并插入file中指定的數(shù)據(jù) * * @param file * @throws Exception */ protected void setUpDataSet(String file) throws Exception { IDataSet dataset = new FlatXmlDataSet(new ClassPathResource(file) .getFile()); DatabaseOperation.CLEAN_INSERT.execute(conn, dataset); } /** * 驗(yàn)證file中包含的表中的數(shù)據(jù)和數(shù)據(jù)庫(kù)中的相應(yīng)表的數(shù)據(jù)是否一致 * * @param file * @throws Exception */ protected void verifyDataSet(String file) throws Exception { IDataSet expected = new FlatXmlDataSet(new ClassPathResource(file) .getFile()); IDataSet dataset = conn.createDataSet(); for (String tableName : expected.getTableNames()) { Assertion.assertEquals(expected.getTable(tableName), dataset .getTable(tableName)); } } /** * 清空指定的表中的數(shù)據(jù) * * @param tableName * @throws Exception */ protected void clearTable(String tableName) throws Exception { DefaultDataSet dataset = new DefaultDataSet(); dataset.addTable(new DefaultTable(tableName)); DatabaseOperation.DELETE_ALL.execute(conn, dataset); } /** * 驗(yàn)證指定的表為空 * * @param tableName * @throws DataSetException * @throws SQLException */ protected void verifyEmpty(String tableName) throws DataSetException, SQLException { Assert.assertEquals(0, conn.createDataSet().getTable(tableName) .getRowCount()); } } |
使用:
@Test public void updateUser() throws Exception { setUpDataSet("com/taobao/dbunit/dao/user001.xml"); User user = new User(); user.setNick("user001"); user.setPassword("password002"); userDao.update(user); verifyDataSet("com/taobao/dbunit/dao/user001_updated.xml"); } |
posted on 2013-12-23 10:13 順其自然EVO 閱讀(229) 評(píng)論(0) 編輯 收藏 所屬分類(lèi): 數(shù)據(jù)庫(kù)