遇到问题多思考、多查阅、多验证,方能有所得,再勤快点乐于分享,才能写出好文章。
单元测试(unit testing):是指对软件中的最小可测试单元进行检查和验证。
这个定义有点抽象,这里举几个单元测试的特性,大家感受一下:一般是一个函数配几个单元测试、单元测试不应该依赖外部系统、单元测试运行速度很快、单元测试不应该造成测试环境的脏数据、单元测试可以重复运行。
单元测试使得我们可以放心修改、重构业务代码,而不用担心修改某处代码后带来的副作用。
单元测试可以帮助我们反思模块划分的合理性,如果一个单元测试写得逻辑非常复杂、或者说一个函数复杂到无法写单测,那就说明模块的抽象有问题。
单元测试使得系统具备更好的可维护性、具备更好的可读性;对于团队的新人来说,阅读系统代码可以从单元测试入手,一点点开始后熟悉系统的逻辑。
我们基于unit-test-demo这个项目进行单元测试的实践。
最开始写单测的时候,要连着DEV的数据库,这时候会有两个烦恼:网络有问题的时候单测运行不通过、数据库里造成脏数据的时候会导致应用程序异常。这里我们选择H2进行DAO层的单元测试。有如下几个步骤:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:jdbc="http://www.springframework.org/schema/jdbc" xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/jdbc
- http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
- <!-- 初始化数据表结构 -->
- <jdbc:initialize-database data-source="dataSource">
- <jdbc:script location="classpath:h2/schema.sql" encoding="UTF-8"/>
- <jdbc:script location="classpath:h2/data-prepare-*.sql" encoding="UTF-8"/>
- </jdbc:initialize-database>
- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
- init-method="init" destroy-method="close">
- <property name="url" value="${user.jdbc.url}"/>
- <property name="username" value="${user.jdbc.username}"/>
- <property name="password" value="${user.jdbc.password}"/>
- <!-- 连接池初始连接数 -->
- <property name="initialSize" value="3"/>
- <!-- 允许的最大同时使用中(在被业务线程持有,还没有归还给druid) 的连接数 -->
- <property name="maxActive" value="30"/>
- <!-- 允许的最小空闲连接数,空闲连接超时踢除过程会最少保留的连接数 -->
- <property name="minIdle" value="3"/>
- <!-- 从连接池获取连接的最大等待时间 5 秒-->
- <property name="maxWait" value="5000"/>
- <!-- 强行关闭从连接池获取而长时间未归还给druid的连接(认为异常连接)-->
- <property name="removeAbandoned" value="true"/>
- <!-- 异常连接判断条件,超过180 秒 则认为是异常的,需要强行关闭 -->
- <property name="removeAbandonedTimeout" value="180"/>
- <!-- 从连接池获取到连接后,如果超过被空闲剔除周期,是否做一次连接有效性检查 -->
- <property name="testWhileIdle" value="true"/>
- <!-- 从连接池获取连接后,是否马上执行一次检查 -->
- <property name="testOnBorrow" value="false"/>
- <!-- 归还连接到连接池时是否马上做一次检查 -->
- <property name="testOnReturn" value="false"/>
- <!-- 连接有效性检查的SQL -->
- <property name="validationQuery" value="SELECT 1"/>
- <!-- 连接有效性检查的超时时间 1 秒 -->
- <property name="validationQueryTimeout" value="1"/>
- <!-- 周期性剔除长时间呆在池子里未被使用的空闲连接, 10秒一次-->
- <property name="timeBetweenEvictionRunsMillis" value="10000"/>
- <!-- 空闲多久可以认为是空闲太长而需要剔除 30 秒-->
- <property name="minEvictableIdleTimeMillis" value="30000"/>
- <!-- 是否缓存prepareStatement,也就是PSCache,MySQL建议关闭 -->
- <property name="poolPreparedStatements" value="false"/>
- <property name="maxOpenPreparedStatements" value="-1"/>
- <!-- 是否设置自动提交,相当于每个语句一个事务 -->
- <property name="defaultAutoCommit" value="true"/>
- <!-- 记录被判定为异常的连接 -->
- <property name="logAbandoned" value="true"/>
- <!-- 网络读取超时,网络连接超时 -->
- <property name="connectionProperties" value="connectTimeout=1000;socketTimeout=3000"/>
- </bean>
- <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="dataSource" ref="dataSource"/>
- <property name="mapperLocations" value="classpath:mybatis/mapper/*Mapper.xml"/>
- <property name="typeAliasesPackage" value="org.learnjava.dq.core.dal.bean"/>
- </bean>
- <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
- <property name="basePackage" value="org.learnjava.dq.core.dal.dao"/>
- <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
- </bean>
- </beans>
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
- <!-- 激活自动代理功能 -->
- <aop:aspectj-autoproxy proxy-target-class="true"/>
- <!-- spring容器启动时,静态配置替换 -->
- <context:property-placeholder location="classpath*:*.properties" ignore-unresolvable="true"/>
- <context:component-scan base-package="org.learnjava.dq.core.dal.dao"/>
- <import resource="test-data-sources.xml"/>
- </beans>
- package org.learnjava.dq.core.dal.dao;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.learnjava.dq.core.dal.bean.UserInfoBean;
- import org.springframework.test.context.ContextConfiguration;
- import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
- import java.util.Date;
- import javax.annotation.Resource;
- import static org.junit.Assert.*;
- /**
- * 作用:
- * User: duqi
- * Date: 2017/6/24
- * Time: 09:33
- */
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:test-h2-applicationContext.xml")
- public class UserInfoDAOTest {
- @Resource
- private UserInfoDAO userInfoDAO;
- @Test
- public void saveUserInfoBean() throws Exception {
- UserInfoBean userInfoBean = new UserInfoBean();
- userInfoBean.setUserId(1003L);
- userInfoBean.setNickname("wangwu");
- userInfoBean.setMobile("18890987675");
- userInfoBean.setSex(1);
- userInfoBean.setUpdateTime(new Date());
- userInfoBean.setCreateTime(new Date());
- int rows = userInfoDAO.saveUserInfoBean(userInfoBean);
- assertEquals(1, rows);
- }
- @Test
- public void updateUserInfoBean() throws Exception {
- }
- @Test
- public void getUserInfoBeanByUserId() throws Exception {
- }
- @Test
- public void getUserInfoBeanByMobile() throws Exception {
- }
- @Test
- public void listUserInfoBeanByUserIds() throws Exception {
- }
- @Test
- public void removeUserInfoBeanByUserId() throws Exception {
- }
- }
Mockito is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API. Mockito doesn’t give you hangover because the tests are very readable and they produce clean verification errors.
- package org.learnjava.dq.biz.manager.impl;
- import org.junit.Before;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.learnjava.dq.biz.domain.UserInfo;
- import org.learnjava.dq.biz.manager.UserInfoManager;
- import org.learnjava.dq.core.dal.bean.UserInfoBean;
- import org.learnjava.dq.core.dal.dao.UserInfoDAO;
- import org.mockito.InjectMocks;
- import org.mockito.Mock;
- import org.mockito.MockitoAnnotations;
- import org.mockito.runners.MockitoJUnitRunner;
- import static org.junit.Assert.*;
- import static org.mockito.Mockito.*;
- /**
- * 作用:
- * User: duqi
- * Date: 2017/6/24
- * Time: 09:55
- */
- @RunWith(MockitoJUnitRunner.class)
- public class UserInfoManagerImplTest {
- @Mock //用于定义被Mock的组件
- private UserInfoDAO userInfoDAO;
- @InjectMocks //用于定义待测试的组件
- private UserInfoManager userInfoManager = new UserInfoManagerImpl();
- private UserInfo userInfoToSave;
- @Before
- public void setUp() throws Exception {
- //用于初始化@Mock注解修饰的组件
- MockitoAnnotations.initMocks(this);
- userInfoToSave = new UserInfo();
- userInfoToSave.setMobile("18978760099");
- userInfoToSave.setUserId(7777L);
- userInfoToSave.setSex(1);
- }
- @Test
- public void saveUserInfo_case1() throws Exception {
- //step1 准备数据和动作
- doReturn(1).when(userInfoDAO).saveUserInfoBean(any(UserInfoBean.class));
- //step2 运行待测试模块
- Boolean res = userInfoManager.saveUserInfo(userInfoToSave);
- //step3 验证测试结果
- assertTrue(res);
- }
- @Test
- public void saveUserInfo_case2() throws Exception {
- //step1 准备数据和动作
- doReturn(0).when(userInfoDAO).saveUserInfoBean(any(UserInfoBean.class));
- //step2 运行待测试模块
- Boolean res = userInfoManager.saveUserInfo(userInfoToSave);
- //step3 验证测试结果
- assertFalse(res);
- }
- @Test
- public void updateUserInfo() throws Exception {
- }
- @Test
- public void getUserInfoByUserId() throws Exception {
- }
- @Test
- public void getUserInfoByMobile() throws Exception {
- }
- @Test
- public void listUserInfoByUserIds() throws Exception {
- }
- @Test
- public void removeUserInfoByUserId() throws Exception {
- }
- }
来源: https://juejin.im/entry/5a1ec3c951882546d71f1b1c