本ut的实践,未不需要加载过多的springbean,除了需要测试的方法,其他均是mock的。这样大大提高了启动测试的时间,但是编写mock,也同样需要大量的时间。所有我们只对于复杂业务写了单元测试,下面为代码实现



import org.apache.commons.io.FileUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
//将bean的数据,数据结果放在文件中,json格式加载出来
@RunWith(SpringRunner.class)
public class MockBaseTest {

private final static Logger LOGGER = LoggerFactory.getLogger(MockBaseTest.class);/**
*
* @param path commond json 文件路径
* @param clazz commond 类
* @return
*/

@SuppressWarnings("unchecked")
public static <T> T loadObject(String path,Class<T> clazz){

String basePath = MockBaseTest.class.getResource("/").getPath();

String commandStr = null;
try {
commandStr = FileUtils.readFileToString(new File(basePath + path), Charset.defaultCharset());
return JsonHelper.fromJson(commandStr,clazz);
} catch (IOException e) {
LOGGER.error("loadCommand",e);
}
return null;
}

public static <T> T loadObject(String path, Type typeOfT){
String basePath = MockBaseTest.class.getResource("/").getPath();
try {
String commandStr = FileUtils.readFileToString(new File(basePath + path), Charset.defaultCharset());
return JsonHelper.fromJson(commandStr,typeOfT);
} catch (IOException e) {
LOGGER.error("loadCommand",e);
}
return null;
}
}



import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;

//TestServiceImpl里所有的需要注入的bean都要mock,为了实际test代码地方简介一点,这里提个base类
@Import(TestServiceBaseTest.BeanConfiguration.class)
public class TestServiceBaseTest extends MockBaseTest {
@Autowired
public TestService testService;

@TestConfiguration
static class BeanConfiguration {
@Bean
public TestService testService() {
return new TestServiceImpl();
}
}

//------------------------------------------- bean ------------------------------------------------------------------------
@MockBean
public AaService aaService;
@MockBean
public BbService bbService;
@MockBean
public CcDao CcDao;


}



import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.gson.reflect.TypeToken;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;


public class TestServicePayLockTest extends TestServiceBaseTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();

private static Long LOCK_SECONDS = 5L;
//替代redis,可过期功能
private Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(LOCK_SECONDS, TimeUnit.SECONDS)
.build();
//需要往数据库插入数据,并get时,这个可替代数据库功能,本次下面代码没有使用
private List<Test> dataList = new ArrayList();

@Test
public void payLockAmountError() {
expectedException.expect(RuntimeErrorException.class);
expectedException.expectMessage("数据发生变化,请刷新重试!");
TypeToken<PayLock> typeToken = new TypeToken<PayLock>() {
};
String number = "1";
BigDecimal amount = new BigDecimal("100.1");
PayLock payLock = loadResource("command/payment/test_data.json", typeToken.getType());
when(ccDao.find(anyString()))
.thenReturn(payLock.orders);
when(ccDao.find2(anyList())).thenReturn(payLock.items);
setRedicLockMock();
testService.payLock(number, amount);
}

@Test
public void payLockFailError() throws InterruptedException {

TypeToken<PayLock> typeToken = new TypeToken<PayLock>() {
};
String number = "1";
BigDecimal amount = new BigDecimal("6");
PayLock payLock = loadResource("command/payment/test_data.json", typeToken.getType());
when(ccDao.find(eq("1"))).thenReturn(payLock.orders);
when(ccDao.find(not(eq("1")))).thenReturn(payLock.orders2);
setRedicLockMock();
when(ccDao.find2(anyList())).thenReturn(payLock.items);
testService.payLock(number, amount);
try {
testService.payLock("2", amount);
} catch (Exception e) {
Assert.assertEquals(e.getClass(), RuntimeException.class);
Assert.assertEquals(e.getMessage(), "数据被锁定,请稍后刷新重试!");
}
Assert.assertEquals(3, cache.size());
Thread.sleep(1000 * LOCK_SECONDS);
testService.payLock("2", amount);
}

@Test
public void payLockReleaseError() {
expectedException.expect(RuntimeException.class);
expectedException.expectMessage("数据被锁定,请稍后刷新重试!");

TypeToken<PayLock> typeToken = new TypeToken<PayLock>() {
};
String number = "1";
BigDecimal amount = new BigDecimal("6");
PayLock payLock = loadResource("command/payment/test_data.json", typeToken.getType());
when(ccDao.find(anyString())).thenReturn(payLock.orders);
when(redisLockUtil.getLock(not(eq(PayCode.PAY_LOCK + "3")), anyString(), eq(PayCode.LOCK_TIME), eq(PayCode.LOCK_TIME_UNIT))).thenReturn(true);
when(redisLockUtil.getLock(eq(PayCode.PAY_LOCK + "3"), anyString(), eq(PayCode.LOCK_TIME), eq(PayCode.LOCK_TIME_UNIT))).thenReturn(false);
doThrow(new RuntimeException("锁释放失败!")).when(redisLockUtil).releaseLock(eq("2"), anyString());
when(ccDao.find2(anyList())).thenReturn(payLock.items);
testService.payLock(number, amount);

}

private void setRedicLockMock() {
//when invoke return method,do something
when(redisLockUtil.getLock(anyString(), anyString(), anyLong(), any(TimeUnit.class))).then((a) -> {
String key = a.getArgument(0);
String value = cache.getIfPresent(key);
if (value == null) {
cache.put(key, a.getArgument(1));
return true;
}
return false;
});
//when invoke viod method,do something
doAnswer(answer -> {
String ifPresent = cache.getIfPresent(answer.getArgument(0));
if (ifPresent != null && ifPresent.equals(answer.getArgument(1))) {
cache.invalidate(answer.getArgument(0));
} else if(ifPresent != null && ifPresent(answer.getArgument(0))){
throw new RuntimeExcetion("锁释放失败!");
}
return null;
}).when(redisLockUtil).releaseLock(anyString(), anyString());
}

// 本次ut需要所有的对象,这里构建一个内部类,有用加载json文件里的配置
class PayLock {
List<Order> orders;
List<Order> orders2;
List<Item> items;
}

}