简单应用
spring单元测试
需要首先引入jar包并指定测试类SpringJUnit4ClassRunner
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AppTest {
@Autowired
private ResumeMapper resumeMapper;
@Test
public void testmp() {
List<Resume> resumes = resumeMapper.selectList(null);
for (int i = 0; i < resumes.size(); i++) {
Resume resume = resumes.get(i);
System.out.println(resume);
}
}
}
springboot单元测试
springboot对单元测试的支持十分完善,需要引入的jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
使用方式:在测试类的类头部需要添加@RunWith(SpringRunner.class)
和@SpringBootTest
注解,在测试方法的顶端添加@Test即可
@RunWith(SpringRunner.class)
@SpringBootTest
public class SysUserServiceImplTest {
@Autowired
private SysUserService sysUserService;
@Test
public void queryAllPerms() throws Exception {
List<String> allPerms = sysUserService.queryAllPerms(1L);
System.out.println(allPerms);
}
}
单元测试意义
单元测试主要是测试程序员自己编写的代码逻辑的正确性,而非是端到端的集成测试,它不需要测试所依赖的外部系统的逻辑正确性。
测试用例应该什么时候跑?
- 功能实现之后
- 重构之后,单元测试是保证重构不出错最有效的手段
技术选型:junit4+jmockit1.5+jacoco
Junit4
用于编写单元测试,通过编写正常的业务流程,并进行相应的断言测试,来验证接口。
JMockit
是google code上面的一个java单元测试mock项目,它很方便地让你对单元测试中的final类,静态方法,构造方法进行mock,主要是通过jmockit模拟一个返回对象。
jacoco
用于统计单元测试覆盖率,其他相似工具如Cobertura、Emma、Clover。
单元测试不要盲目追求覆盖率,单元测试指的是类或方法,而不是模块或系统。
问题现状
目前组内成员开发过程中,既没有单元测试也没有Code Review流程。
写好代码直接提交,然后丢给黑盒测试组,测试发现问题就提bug给开发团队再修改,测不出的问题就留在线上出了问题在修复。
什么是mock
如果代码中依赖了外部系统或者不可控组件,比如,需要依赖数据库、网络通信、文件系统等,那我们就需要将被测代码与外部系统解依赖,而这种解依赖的方法就叫作mock。
mock 的方式主要有两种,手动 mock 和利用框架 mock。
依赖注入
是编写可测试性代码的最有效手段。通过依赖注入,我们在编写单元测试的时候,可以通过 mock 的方法解依赖外部服务。
有一些典型的不好测试的代码,注意编码时规避掉
未决行为,即代码的输出结果是不确定的,如时间、随机数相关的
public class OrderService {
public long caculateDelayDays(Date dueTime) {
long currentTimestamp = System.currentTimeMillis();
if (dueTime.getTime() >= currentTimestamp) {
return 0;
}
long delayTime = currentTimestamp - dueTime.getTime();
long delayDays = delayTime / 86400;
return delayDays;
}
}
以上判断是否超期的逻辑最好能封装到函数,这样测试时可以直接mock结果
public class OrderService {
public long caculateDelayDays(Date dueTime) {
long currentTimestamp = System.currentTimeMillis();
if (!isExpired(dueTime, currentTimestamp)) {
return 0;
}
long delayTime = currentTimestamp - dueTime.getTime();
long delayDays = delayTime / 86400;
return delayDays;
}
private boolean isExpired(Date dueTime, long currentTimestamp) {
if (dueTime.getTime() >= currentTimestamp) {
return false;
}
return true;
}
}
JMockit测试的两种实现方式
JMockit有两种测试方式,一种是基于行为的,一种是基于状态的测试。
- 基于行为的mock,对mock目标代码的行为进行模仿,更像
黑盒测试
。 - 基于状态的mock,是站在目标测试代码内部的。可以对传入的参数进行检查、匹配,才返回某些结果,类似白盒。
基于状态的 new MockUp基本上可以mock任何代码或逻辑。
final UserBaseInfo userBaseInfo = new UserBaseInfo();
new NonStrictExpectations(){
{
new MockUp<SessionContainer>() {
@Mock
public SessionCache getSession() {
return new SessionCache();
}
}.getMockInstance();
new MockUp<SessionCache>() {
@Mock
public UserBaseInfo getUserBaseInfo() {
return userBaseInfo;
}
}.getMockInstance();
}
};
类命名和方法命名以Test结尾,方法添加注解@Test,需要引入的对象添加注解@Injectable
public class UserHeartRateServieTest{
@Injectable
private UserHeartRateResultDao userHeartRateResultDaoMock;
@Test
public void insertUserHeartRateTest(){
new NonStrictExpectations(){
{
userHeartRateResultDaoMock.insertUserHeartRateResult((UserHeartRateResult)any);
}
};
}
}
new NonStrictExpectations(){
{
userHeartRateResultDaoMock.selectByIdOrUserId(anyInt,anyInt);
result = userHeartRateResult;
userHeartRateDataDaoMock.getUserHeartRateDataListByGroupId(anyString);
result = list;
}
};
pom.xml单元测试配置
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.googlecode.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.4.201502262128</version>
<configuration>
<includes>
</includes>
<excludes>
</excludes>
</plugin>
Expectations与NonStrictExpectations
Expectations块里声明的mock方法,是一定要被执行的,如果没有被执行,会认为整个测试case不通过;NonStrictExpectations就没有这个限制
new Expectations(){
{
executorDao.getExecutorById(anyInt);
result = rtnMap;
teamDao.getTeamById(anyInt);
result = teamMap;
teamDao.getEnergyByGreenAreaId(anyInt);
result = 1;
}
};
对于需要引入工具类的代码行,可以作为参数传进来
参考