本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; } }