DAO test

简介:
至此,一个基于MVC的基本 Android应用程序已经初步形成了。
  下面我们来实现一个具有TabHost的布局的典型Android应用,由于我们基本上可以不考虑Android 4.x以前的版本,因此我对TabHost布局的实现将采用Fragment来实现,而不是采用旧的ActivityGroup来实现。
  同时,我们希望我们的应用程序可以适用于不同的项目,因此需要TabHost上的图片及文字可以非常方便的进行更换。我们采用下部有5个选项的布局,其中中间的选项可以突出显示,选中某个选项,目前仅显示对应Fragmentation的名字。
  好了,需求基本说清楚了,下面我们就开始一步步实现吧。
  首先是基于Fragment的TabHost布局实现,原理很简单,在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" >
<!-- 实现Tab标签的居底主要是通过设置属性 android:layout_weight="1" -->
<!-- 还要注意FrameLayout标签的位置,要写在TabWidget标签的前面 -->
<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>
好的单元测试应该是原子性的,独立的,不应依赖其他测试和上下文,但是要测试数据读写是否正确,就必须涉及初始数据的加载,数据修改的还原等操作。对于初始数据的加载,手动输入很麻烦,一个解决方案就是使用Dbunit,从Xml文件甚至Excel中加载初始数据到数据库,是数据库的值达到一个已知状态。同时还可以使用Dbunit,对数据库的结果状态进行判断,保证和期望的一致。数据修改的还原,可以依赖Spring TransactionalTests,在测试完成后回滚数据库。
  Dbunit还可以对数据的现有数据进行备份,还原,清空现有数据,一个好的测试实践是每一个开发人员一个测试数据库,进而对数据库的数据状态有更好的控制,但现实可能会是共享同一个测试库,所以这种情况下,测试的编写必须多做一些考虑。
   待测试的类:
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) , 通过这种方式获得数据库连接初始化Dbunit,能够保证Dbunit使用的数据连接和当前事务的数据库连接相同,保证能够在参与到事务中。Spring的TransactionManager会在开始事务时把当前连接保存到ThreadLocal中,DataSourceUtils.getConnection方法,首先从ThreadLocal中获取连接。
  user001.xml
  Xml代码
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<user nick="user001" password="password001" />
</dataset>
  使用dbunit,可以通过xml文件定义数据集,也可以使用其他方式定义,比如Excel,编程方式。
  Dbunit的主要构件
  IDatabaseConnection
  数据库链接。实现类有DatabaseConnection 和DatabaseDataSourceConnection ,执行数据库操作时需要一个连接。
  IDataSet
  数据集,数据集可以从Xml文件Excel等外部文件获取,也可以从数据库查询,或者编程方式构件,数据集可以作为初始数据插入到数据库,也可以作为断言的依据。另外还有IDatatable等辅助类。
  比如在updateUser测试中,使用了QueryDataSet,从数据库中构建一个Dataset,再通过FlatXmlDataSet从Xml文件中构建一个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


  通过定义的静态字段可以获取一组代表一个数据操作的子类对象,比如DatabaseOperation .INSERT,返回 InsertOperation,通过执行execute方法把数据集插入到数据库。例如:
  IDataSet origen = new FlatXmlDataSet(new ClassPathResource(
  "com/taobao/dbunit/dao/user001.xml").getFile());
  DatabaseOperation.INSERT.execute(conn, origen);
  从Xml文件中构建DataSet,使用Insert插入到数据库,初始化测试数据。
   Assertion
  唯一的方法,assertEqual,断言两个数据集或数据表相同。
  更多关于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中包含的表中的数据,并插入file中指定的数据
*
* @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中包含的表中的数据和数据库中的相应表的数据是否一致
*
* @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));
}
}
/**
* 清空指定的表中的数据
*
* @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");
}


最新内容请见作者的GitHub页:http://qaseven.github.io/

相关文章
|
监控 算法 NoSQL
【Linux】四、Linux 进程概念(二)
目录 五、进程状态 5.1 普遍操作系统层面的进程状态(宏观) 5.1.1 什么叫做运行状态 5.1.2 阻塞状态 5.1.2 挂起状态 5.2 Linux 的进程状态(具体) 5.2.1 Linux 内核进程状态源代码 5.2.2 Linux 进程状态查看 5.2.3 Linux 运行状态 R(running) 5.2.4 Linux 睡眠状态 S(sleeping) 5.2.5 Linux 磁盘休眠状态 D(Disk sleep) 5.2.6 Linux 停止状态 T(stopped) 5.2.7 Linux 追踪停止状态 t(tracing stop)
135 0
【Linux】四、Linux 进程概念(二)
|
7天前
|
人工智能 运维 安全
|
5天前
|
人工智能 异构计算
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
|
6天前
|
机器学习/深度学习 人工智能 自然语言处理
B站开源IndexTTS2,用极致表现力颠覆听觉体验
在语音合成技术不断演进的背景下,早期版本的IndexTTS虽然在多场景应用中展现出良好的表现,但在情感表达的细腻度与时长控制的精准性方面仍存在提升空间。为了解决这些问题,并进一步推动零样本语音合成在实际场景中的落地能力,B站语音团队对模型架构与训练策略进行了深度优化,推出了全新一代语音合成模型——IndexTTS2 。
589 20
|
12天前
|
人工智能 JavaScript 测试技术
Qwen3-Coder入门教程|10分钟搞定安装配置
Qwen3-Coder 挑战赛简介:无论你是编程小白还是办公达人,都能通过本教程快速上手 Qwen-Code CLI,利用 AI 轻松实现代码编写、文档处理等任务。内容涵盖 API 配置、CLI 安装及多种实用案例,助你提升效率,体验智能编码的乐趣。
954 110
|
6天前
|
人工智能 测试技术 API
智能体(AI Agent)搭建全攻略:从概念到实践的终极指南
在人工智能浪潮中,智能体(AI Agent)正成为变革性技术。它们具备自主决策、环境感知、任务执行等能力,广泛应用于日常任务与商业流程。本文详解智能体概念、架构及七步搭建指南,助你打造专属智能体,迎接智能自动化新时代。