Dao单元测试技术方案:H2+spring-test+spring-test-dbunit+testng
一.* 技术方案的选择*
H2:内存数据库,支持标准SQL,相当于把数据库本地化,可以避免对测试环境的依赖,也可以提升单测的速度。
spring-test: 提供了@DirtiesContex AbstractTestNGSpringContextTests等诸多注释和基类,可以用来简化单元测试。
spring-test-dbunit:提供了对dbunit的注解封装,可以用来提供测试数据和进行数据验证。
testng: 提供了相比junit更丰富的测试功能,使用起来更方便。
二.项目实战 1. 新建一个名为DaoTestExample的maven工程,结构如下图:
2.在test目录下新建一个schema.sql文件,写入一个User表的建表语句,这个文件是用来初始化H2数据库,在测试用例执行前建好所需要的表结构。
CREATE TABLE `User` ( `AutoId` bigint(20) NOT NULL AUTO_INCREMENT, `UserId` bigint(20) NOT NULL COMMENT '用户Id', `UserName` varchar(64) NOT NULL COMMENT '用户姓名', `Age` int(10) NOT NULL COMMENT '年龄', `PointValue` int(11) NOT NULL DEFAULT '0' COMMENT '积分', `Status` smallint(6) NOT NULL DEFAULT '0' COMMENT '记录可用状态', `CreateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建日期', `LastModifyTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后修改日期', PRIMARY KEY (`AutoId`) );3.h2数据源配置初始化
@Configuration @MapperScan(basePackages = "dao.mapper") @ComponentScan({"dao"}) public class H2Config { @Bean public DataSource h2DataSource(){ EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); EmbeddedDatabase database = builder.setType(EmbeddedDatabaseType.H2) .addScript("classpath:schema.sql") //启动时初始化建表语句 .build(); return database; } @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); //加载所有的sql mapper文件 Resource[] mapperLocations = resolver.getResources("classpath:/mapper/*.xml"); sessionFactory.setMapperLocations(mapperLocations); return sessionFactory.getObject(); } @Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); }4.dao层测试基类
@SpringApplicationConfiguration(classes = {H2Config.class}) @TestExecutionListeners({ DbUnitTestExecutionListener.class }) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @DbUnitConfiguration(databaseConnection = { "h2DataSource" }) public class DaoBaseTest extends AbstractTestNGSpringContextTests { }@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) 会在每个测试用例执行完后清除数据库。
@TestExecutionListeners({ DbUnitTestExecutionListener.class }) 使得dbunit的注解生效,不然spring-test-dbunit提供的注解不会启用。
@DbUnitConfiguration(databaseConnection = { “h2DataSource” }) 指定数据源
由于项目中使用的是testng,为了 获取dao对象,需要继承一个spring-test中的基类AbstractTestNGSpringContextTests,利用此基类中的applicationContext来初始化、获取注入对象。
不过仅仅继承AbstractTestNGSpringContextTests还无法实现对象注入,我们需要引入 @MapperScan 和 @ComponentScan
所有的dao测试都要继承DaoBaseTest
5.新建dbunit初始化数据库和校验数据所需的xml文件
user-setUpData.xml 文件,测试用例执行之前所需要准备的测试数据 User是表名,UserId=”200” UserName=”tony” Age=”25” pointValue=”2000” 是表中的部分字段(下同)
<?xml version="1.0" encoding="UTF-8" ?> <dataset> <User UserId="200" UserName="tony" Age="25" pointValue="2000" /> </dataset>addUser-expectedData.xml文件,用来校验插入数据库的数据是否符合预期。
<?xml version="1.0" encoding="UTF-8" ?> <dataset> <User UserId="200" UserName="tony" Age="25" pointValue="2000" /> </dataset>updatePoint-exceptedData.xml 文件,用来校验更新积分后的数据是否符合预期
<?xml version="1.0" encoding="UTF-8" ?> <dataset> <User UserId="200" UserName="tony" Age="25" pointValue="2500" /> </dataset>6.被测试的dao类
public interface UserDao { int addUser(User user); int updatePointValue(Long userId,Long pointValue); User getUserInfo(Long userId); } @Repository public class UserDaoImpl implements UserDao { @Autowired private UserMapper userMapper; @Override public int addUser(User user) { return userMapper.addUser(user); } @Override public int updatePointValue(Long userId, Long pointValue) { return userMapper.updatePointValue(userId,pointValue); } @Override public User getUserInfo(Long userId) { return userMapper.getUserInfo(userId); } }7.userMapper.xml 文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="dao.mapper.UserMapper"> <resultMap id="BaseResultMap" type="dao.entity.User"> <id column="AutoId" property="id" jdbcType="BIGINT"/> <result column="UserId" property="userId" jdbcType="BIGINT"/> <result column="UserName" property="name" jdbcType="VARCHAR"/> <result column="Age" property="age" jdbcType="INTEGER"/> <result column="PointValue" property="pointValue" jdbcType="INTEGER"/> <result column="Status" property="status" jdbcType="SMALLINT"/> <result column="CreateTime" property="createTime" jdbcType="TIMESTAMP"/> <result column="LastModifyTime" property="lastModifyTime" jdbcType="TIMESTAMP"/> </resultMap> <insert id="addUser" useGeneratedKeys="true" keyProperty="id" parameterType="dao.entity.User"> INSERT INTO User(UserId,UserName,Age,PointValue,Status,CreateTime,LastModifyTime) VALUES(#{userId},#{name},#{age},#{pointValue},0,now(),now()) </insert> <update id="updatePointValue"> UPDATE User SET PointValue = #{pointValue} WHERE UserId=#{userId} </update> <select id="getUserInfo" resultMap = "BaseResultMap"> SELECT UserId,UserName,Age,PointValue FROM User WHERE UserId=#{userId} </select> </mapper>8.User实体类
public class User { private Long id; private Long userId; private String name; private Integer age; private Integer pointValue; private Integer status; private Date createTime; private Date lastModifyTime; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getPointValue() { return pointValue; } public void setPointValue(Integer pointValue) { this.pointValue = pointValue; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public Date getLastModifyTime() { return lastModifyTime; } public void setLastModifyTime(Date lastModifyTime) { this.lastModifyTime = lastModifyTime; } }9.测试用例
public class UserDaoTest extends DaoBaseTest { @Autowired private UserDao userDao; @Test @ExpectedDatabase(table = "User", assertionMode= DatabaseAssertionMode.NON_STRICT, value= "/data/addUser-expectedData.xml") public void testAddUser() throws Exception { User user = new User(); user.setUserId(200L); user.setAge(25); user.setPointValue(2000); user.setName("tony"); int result = userDao.addUser(user); Assert.assertEquals(result,1); } @Test @DatabaseSetup("/data/user-setUpData.xml") @ExpectedDatabase(table = "User", assertionMode= DatabaseAssertionMode.NON_STRICT, value= "/data/updatePoint-exceptedData.xml") public void testUpdatePointValue() throws Exception { int result = userDao.updatePointValue(200L,2500L); Assert.assertEquals(result,1); } @Test @DatabaseSetup("/data/user-setUpData.xml") public void testGetUserInfo() throws Exception { User user = userDao.getUserInfo(200L); Assert.assertNotNull(user); Assert.assertEquals(user.getAge().intValue(),25); Assert.assertEquals(user.getName(),"tony"); Assert.assertEquals(user.getUserId().intValue(),200); Assert.assertEquals(user.getPointValue().intValue(),2000); } }三、pom依赖
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>1.3.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>1.3.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.2.4.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>com.github.springtestdbunit</groupId> <artifactId>spring-test-dbunit</artifactId> <version>1.3.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.dbunit</groupId> <artifactId>dbunit</artifactId> <version>2.5.3</version> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.195</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.2.4.RELEASE</version> </dependency> </dependencies>四、总结 此技术方案可以实现单元测试与测试环境的隔离,自动化测试、数据隔离等在重新认识单元测试一篇中提到的单元测试规范。
更多内容欢迎关注个人微信公众号,一起成长!