段落引用很多公司都要求程序员写单元测试,并要求单元测试的代码覆盖率达到80%到90%.但是很多公司的程序员都是被需求和bug赶着跑的,自然没有精力去写单元测试.但是单元测试对于很多比较成熟的项目来说是有必要的,毕竟要用上十几年,甚至几十年.但是对于那种代码生命周期在短短几个月的功能,比如为了某个节日某个活动写的临时程序能跑通即可.
一.什么代码需要写单元测试
- 复杂了业务逻辑即Service需要写单元测试
- 复杂的算法
- 调用比较多的工具类
- dao层
二. 如何进行单元测试的编写
-
我们测试的是这个类方法的逻辑,不是对应这个类依赖的底层逻辑,即我们测试的不是整个模块 即如果,我们要对 UserServiceImpl 这个类写 UserServiceImplTest 单元测试类, UserServiceImplTest 要测的是 UserServiceImpl这个类本身的逻辑
-
基于此我们, 所以我们要把 UserServiceImpl类依赖的子模块都mock掉,来测本身的逻辑.
-
单元测试是做白盒测试, 所以我们写测试用例的时候需要做代码覆盖,当某个方法,会根据某些参数,或者依赖模块的返回数据走不同的逻辑时,我们要写多个testcase 方法.
-
针对数据访问层的操作,可以使用两种方案,一个是mock jdbc ,把数据库或者redis操作变成内存操作, 另外一种方案是, 在Test 的 before , after 的方法中做对于数据的初始化,和数据的清理
三. mock 数据比较好用的框架
compile 'org.powermock:powermock-module-junit4:2.0.7'
compile 'org.powermock:powermock-api-mockito2:2.0.7'
compile 'org.mockito:mockito-core:3.3.3'
mockito 是个比较好的mockito工具,他可以mockito类和接口,你只需要管你需要调用的方法返回值即可. 但是它不支持 静态方法,和final方法,所以我们一般把 powermock 和 mockito 搭配着用
下面是一个单元测试的例子
package run.halo.app.service.impl;
import com.qiniu.common.Zone;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import run.halo.app.cache.AbstractStringCacheStore;
import run.halo.app.model.entity.Option;
import run.halo.app.model.properties.QiniuOssProperties;
import run.halo.app.repository.OptionRepository;
import run.halo.app.service.OptionService;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
/**
* OptionService test.
*
* @author johnniang
* @date 3/22/19
*/
@RunWith(MockitoJUnitRunner.class)
public class OptionServiceImplTest {
// ## 这个服务类依赖的底层逻辑
@Mock
private OptionRepository optionRepository;
// ## 这个服务类依赖的底层逻辑
@Mock
private AbstractStringCacheStore cacheStore;
//## 被测试的服务类
@InjectMocks
private OptionServiceImpl optionService;
@Test
public void getQiniuAutoZoneTest() {
getQiniuZoneTest("", Zone.autoZone());
}
@Test
public void getQiniuAutoZoneOfNullOptionTest() {
getQiniuZoneTest(Zone.autoZone(), null);
}
@Test
public void getQiniuZ0ZoneTest() {
getQiniuZoneTest("z0", Zone.zone0());
}
@Test
public void getQiniuZ1ZoneTest() {
getQiniuZoneTest("z1", Zone.zone1());
}
@Test
public void getQiniuZ2ZoneTest() {
getQiniuZoneTest("z2", Zone.zone2());
}
@Test
public void getQiniuAs0ZoneTest() {
getQiniuZoneTest("as0", Zone.zoneAs0());
}
@Test
public void getQiniuNa0ZoneTest() {
getQiniuZoneTest("na0", Zone.zoneNa0());
}
private void getQiniuZoneTest(String region, Zone actualZone) {
getQiniuZoneTest(actualZone, new Option("", region));
}
private void getQiniuZoneTest(Zone actualZone, Option option) {
QiniuOssProperties zoneProperty = QiniuOssProperties.OSS_ZONE;
// 未你这个测试方法需要调用方法定义mock的返回值 ,当然某个方法你不需要调用即不用定义
given(optionRepository.findByKey(zoneProperty.getValue())).willReturn(Optional.ofNullable(option));
Map<String, Object> optionMap = new HashMap<>(1);
optionMap.put(zoneProperty.getValue(), Optional.ofNullable(option).map(Option::getValue).orElse(null));
// 未你这个测试方法需要调用方法定义mock的返回值 ,当然某个方法你不需要调用即不用定义
given(cacheStore.getAny(OptionService.OPTIONS_KEY, Map.class)).willReturn(Optional.of(optionMap));
// When
Zone zone = optionService.getQnYunZone();
// Then
then(cacheStore).should().getAny(OptionService.OPTIONS_KEY, Map.class);
assertNotNull(zone);
assertThat(zone.getRegion(), equalTo(actualZone.getRegion()));
}
}