DAO test
至此,一個基于MVC的基本Android應(yīng)用程序已經(jīng)初步形成了。
下面我們來實現(xiàn)一個具有TabHost的布局的典型Android應(yīng)用,由于我們基本上可以不考慮Android 4.x以前的版本,因此我對TabHost布局的實現(xiàn)將采用Fragment來實現(xiàn),而不是采用舊的ActivityGroup來實現(xiàn)。
同時,我們希望我們的應(yīng)用程序可以適用于不同的項目,因此需要TabHost上的圖片及文字可以非常方便的進行更換。我們采用下部有5個選項的布局,其中中間的選項可以突出顯示,選中某個選項,目前僅顯示對應(yīng)Fragmentation的名字。
好了,需求基本說清楚了,下面我們就開始一步步實現(xiàn)吧。
首先是基于Fragment的TabHost布局實現(xià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" > <!-- 實現(xiàn)Tab標(biāo)簽的居底主要是通過設(shè)置屬性 android:layout_weight="1" --> <!-- 還要注意FrameLayout標(biāo)簽的位置,要寫在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> |
好的單元測試應(yīng)該是原子性的,獨立的,不應(yīng)依賴其他測試和上下文,但是要測試數(shù)據(jù)讀寫是否正確,就必須涉及初始數(shù)據(jù)的加載,數(shù)據(jù)修改的還原等操作。對于初始數(shù)據(jù)的加載,手動輸入很麻煩,一個解決方案就是使用Dbunit,從Xml文件甚至Excel中加載初始數(shù)據(jù)到數(shù)據(jù)庫,是數(shù)據(jù)庫的值達(dá)到一個已知狀態(tài)。同時還可以使用Dbunit,對數(shù)據(jù)庫的結(jié)果狀態(tài)進行判斷,保證和期望的一致。數(shù)據(jù)修改的還原,可以依賴Spring TransactionalTests,在測試完成后回滾數(shù)據(jù)庫。
Dbunit還可以對數(shù)據(jù)的現(xiàn)有數(shù)據(jù)進行備份,還原,清空現(xiàn)有數(shù)據(jù),一個好的測試實踐是每一個開發(fā)人員一個測試數(shù)據(jù)庫,進而對數(shù)據(jù)庫的數(shù)據(jù)狀態(tài)有更好的控制,但現(xiàn)實可能會是共享同一個測試庫,所以這種情況下,測試的編寫必須多做一些考慮。
待測試的類:
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()}); } } |
單元測試:
需要注意的地方就是,DataSourceUtils.getConnection(datasource) , 通過這種方式獲得數(shù)據(jù)庫連接初始化Dbunit,能夠保證Dbunit使用的數(shù)據(jù)連接和當(dāng)前事務(wù)的數(shù)據(jù)庫連接相同,保證能夠在參與到事務(wù)中。Spring的TransactionManager會在開始事務(wù)時把當(dāng)前連接保存到ThreadLocal中,DataSourceUtils.getConnection方法,首先從ThreadLocal中獲取連接。
user001.xml
Xml代碼
<?xml version="1.0" encoding="UTF-8"?> <dataset> <user nick="user001" password="password001" /> </dataset> |
使用dbunit,可以通過xml文件定義數(shù)據(jù)集,也可以使用其他方式定義,比如Excel,編程方式。
Dbunit的主要構(gòu)件
IDatabaseConnection
數(shù)據(jù)庫鏈接。實現(xiàn)類有DatabaseConnection 和DatabaseDataSourceConnection ,執(zhí)行數(shù)據(jù)庫操作時需要一個連接。
IDataSet
數(shù)據(jù)集,數(shù)據(jù)集可以從Xml文件Excel等外部文件獲取,也可以從數(shù)據(jù)庫查詢,或者編程方式構(gòu)件,數(shù)據(jù)集可以作為初始數(shù)據(jù)插入到數(shù)據(jù)庫,也可以作為斷言的依據(jù)。另外還有IDatatable等輔助類。
比如在updateUser測試中,使用了QueryDataSet,從數(shù)據(jù)庫中構(gòu)建一個Dataset,再通過FlatXmlDataSet從Xml文件中構(gòu)建一個Dataset,斷言這兩個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 |
通過定義的靜態(tài)字段可以獲取一組代表一個數(shù)據(jù)操作的子類對象,比如DatabaseOperation .INSERT,返回 InsertOperation,通過執(zhí)行execute方法把數(shù)據(jù)集插入到數(shù)據(jù)庫。例如:
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ù)庫,初始化測試數(shù)據(jù)。
Assertion
唯一的方法,assertEqual,斷言兩個數(shù)據(jù)集或數(shù)據(jù)表相同。
更多關(guān)于Dbunit的組件的介紹:http://www.dbunit.org/components.html
PS:使用Oracle的時候,初始化DatabaseConnection需要傳入scheme。new DatabaseConnection(conn,SCHEMA_NAME ) ,SCHMEA_NAME需要大寫。
附件提供所有代碼下載
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)); } /** * 清空file中包含的表中的數(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); } /** * 驗證file中包含的表中的數(shù)據(jù)和數(shù)據(jù)庫中的相應(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); } /** * 驗證指定的表為空 * * @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) 評論(0) 編輯 收藏 所屬分類: 數(shù)據(jù)庫