您好,登錄后才能下訂單哦!
這篇文章主要介紹SpringBoot環(huán)境下junit單元測試速度優(yōu)化方式的示例分析,文中介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們一定要看完!
在項目提測前,自己需要對代碼邏輯進(jìn)行驗證,所以單元測試必不可少。
但是現(xiàn)在的java項目幾乎都是基于SpringBoot系列開發(fā)的,所以在進(jìn)行單元測試時,執(zhí)行一個測試類就要啟動springboot項目,加載上下文數(shù)據(jù),每次執(zhí)行一次測試都要再重新加載上下文環(huán)境,這樣就會很麻煩,浪費時間;在一次項目中,我們使用自己的技術(shù)框架進(jìn)行開發(fā),每次單元測試時都要初始化很多數(shù)據(jù)(例如根據(jù)數(shù)據(jù)模型建立表,加載依賴其它模塊的類),這樣導(dǎo)致每一次單元測試時都會花3-5分鐘時間(MacOs 四核Intel Core i5 內(nèi)存:16g),所以很有必要優(yōu)化單元測試效率,節(jié)約開發(fā)時間。
首先要優(yōu)化單元測試,那要知道單元測試是怎樣執(zhí)行的
引入相關(guān)測試的maven依賴,例如junit,之后在測試方法加上@Test注解即可,在springboot項目測試中還需要在測試類加上@RunWith注解 然后允許需要測試的方法即可
@RunWith 就是一個運行器
@RunWith(JUnit4.class) 就是指用JUnit4來運行
@RunWith(SpringJUnit4ClassRunner.class),讓測試運行于Spring測試環(huán)境
@RunWith(Suite.class) 的話就是一套測試集合,
@ContextConfiguration Spring整合JUnit4測試時,使用注解引入多個配置文件@RunWith
SpringBoot環(huán)境下單元測試一般是加@RunWith(SpringJUnit4ClassRunner.class)注解,SpringJUnit4ClassRunner繼承BlockJUnit4ClassRunner類,然后在測試方式時會執(zhí)行SpringJUnit4ClassRunner類的run方法(重寫了BlockJUnit4ClassRunner的run方法),run方法主要是初始化spring環(huán)境數(shù)據(jù),與執(zhí)行測試方法
在我們項目中,是通過一個RewriteSpringJUnit4ClassRunner類繼承SpringJUnit4ClassRunner,然后@RunWith(RewriteSpringJUnit4ClassRunner.class)來初始化我們框架中需要的數(shù)據(jù),
RewriteSpringJUnit4ClassRunner里面是通過重寫withBefores方法,在withBefores方法中去初始化數(shù)據(jù)的,之后通過run方法最后代理執(zhí)行測試方法
通過上面說明,可以知道每次測試一個方法都要初始化springboot環(huán)境與加載自己框架的數(shù)據(jù),所以有沒有一種方式可以只需要初始化 一次數(shù)據(jù),就可以反復(fù)運行測試的方法呢?
首先每一次單測都需要重新加載數(shù)據(jù),跑完一次程序就結(jié)束了,所以每次測試方法時都要重新加載數(shù)據(jù),
如果只需要啟動一次把環(huán)境數(shù)據(jù)都加載了,然后之后都單元測試方法都使用這個環(huán)境呢那不就能解決這個問題么。
我們是不是可以搞一個服務(wù)器,把基礎(chǔ)環(huán)境與數(shù)據(jù)都加載進(jìn)去,然后每次執(zhí)行單元測試方法時,通過服務(wù)器代理去執(zhí)行這個方法,不就可以了嗎
首先我們可以用springboot的方式啟動一個服務(wù),通常使用的內(nèi)置tomcat作為服務(wù)啟,之后暴露一個http接口,入?yún)樾枰獔?zhí)行的類和方法,然后通過反射去執(zhí)行這個方法;還可以通過啟動jetty服務(wù),通過jetty提供的handler處理器就可以處理請求,jetty相對于tomcat處理請求更加方便
服務(wù)是有了,那怎樣將單元測試方法代理給服務(wù)器呢?前面提到過,通過@RunWith注入的類,在單元測試方法運行時會執(zhí)行@RunWith注入的類相應(yīng)的方法,所以我們可以在@RunWith注入的類里面做文章,拿到測試類與方法,然后通過http訪問服務(wù)器,然后服務(wù)器去代理執(zhí)行測試方法
下面將通過兩種不同方式實現(xiàn),以Jetty為服務(wù)器啟動,與以Tomcat為服務(wù)器啟動
首先編寫服務(wù)啟動類,并在spring容器準(zhǔn)備好后加載我們公司框架相關(guān)數(shù)據(jù),這里使用jetty作為服務(wù)器,下面代碼是核心方法
// 只能寫在測試目錄下,因為寫在應(yīng)用程序目錄下在序列化時,找不到測試目錄下的類-》InvokeRequest類中的Class<?> testClass反序列化不出來 @SpringBootApplication @ComponentScan(value = "包路徑") public class DebugRunner { public static void main(String... args) { SpringApplication.run(DebugRunner.class, args); System.out.println("================================success========================"); } @EventListener public void onReady(ContextRefreshedEvent event) { // 加載框架數(shù)據(jù) } @Bean public JettyServer jettyServer(ApplicationContext applicationContext) { return new JettyServer(port, applicationContext); } }
使用jetty作為服務(wù)器,并且注入處理器HttpHandler
public class JettyServer { private volatile boolean running = false; private Server server; private final Integer port; private final ApplicationContext applicationContext; public JettyServer(Integer port, ApplicationContext applicationContext) { this.port = port; this.applicationContext = applicationContext; } @PostConstruct public void init() { this.startServer(); } private synchronized void startServer() { if (!running) { try { running = true; doStart(); } catch (Throwable e) { log.error("Fail to start Jetty Server at port: {}, cause: {}", port, Throwables.getStackTraceAsString(e)); System.exit(1); } } else { log.error("Jetty Server already started on port: {}", port); throw new RuntimeException("Jetty Server already started."); } } private void doStart() throws Throwable { if (!assertPort(port)) { throw new IllegalArgumentException("Port already in use!"); } server = new Server(port); // 注冊處理的handler server.setHandler(new HttpHandler(applicationContext)); server.start(); log.info("Jetty Server started on port: {}", port); } /** * 判斷端口是否可用 * * @param port 端口 * @return 端口是否可用 */ private boolean assertPort(int port) { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(port); return true; } catch (IOException e) { log.error("An error occur during test server port, cause: {}", Throwables.getStackTraceAsString(e)); } finally { if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { log.error("An error occur during closing serverSocket, cause: {}", Throwables.getStackTraceAsString(e)); } } } return false; } }
HttpHandler處理http請求
public class HttpHandler extends AbstractHandler { private ObjectMapper objectMapper = new ObjectMapper(); private Map<String, Method> methodMap = new ConcurrentHashMap<>(); private final ApplicationContext applicationContext; public HttpHandler(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } private InvokeRequest readRequest(HttpServletRequest request) throws IOException { int contentLength = request.getContentLength(); ServletInputStream inputStream = request.getInputStream(); byte[] buffer = new byte[contentLength]; inputStream.read(buffer, 0, contentLength); inputStream.close(); return objectMapper.readValue(buffer, InvokeRequest.class); } private void registerBeanOfType(Class<?> type) { BeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClassName(type.getName()); ((DefaultListableBeanFactory) (((GenericApplicationContext) applicationContext).getBeanFactory())) .registerBeanDefinition(type.getName(), beanDefinition); } private Method getMethod(Class clazz, String methodName) { String key = clazz.getCanonicalName() + ":" + methodName; Method md = null; if (methodMap.containsKey(key)) { md = methodMap.get(key); } else { Method[] methods = clazz.getMethods(); for (Method mth : methods) { if (mth.getName().equals(methodName)) { methodMap.putIfAbsent(key, mth); md = mth; break; } } } return md; } private InvokeResult execute(InvokeRequest invokeRequest) { Class<?> testClass = invokeRequest.getTestClass(); Object bean; try { bean = applicationContext.getBean(testClass.getName()); } catch (Exception e) { registerBeanOfType(testClass); bean = applicationContext.getBean(testClass.getName()); } InvokeResult invokeResult = new InvokeResult(); Method method = getMethod(testClass, invokeRequest.getMethodName()); try { // 遠(yuǎn)程代理執(zhí)行 method.invoke(bean); invokeResult.setSuccess(true); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { if (!(e instanceof InvocationTargetException) || !(((InvocationTargetException) e).getTargetException() instanceof AssertionError)) { log.error("fail to invoke code, cause: {}", Throwables.getStackTraceAsString(e)); } invokeResult.setSuccess(false); // 記錄異常類 InvokeFailedException invokeFailedException = new InvokeFailedException(); invokeFailedException.setMessage(e.getMessage()); invokeFailedException.setStackTrace(e.getStackTrace()); // 由Assert拋出來的錯誤 if (e.getCause() instanceof AssertionError) { invokeFailedException.setAssertionError((AssertionError) e.getCause()); } invokeResult.setException(invokeFailedException); } catch (Exception e) { log.error("fail to invoke code, cause: {}", Throwables.getStackTraceAsString(e)); invokeResult.setSuccess(false); InvokeFailedException invokeFailedException = new InvokeFailedException(); invokeFailedException.setMessage(e.getMessage()); invokeFailedException.setStackTrace(e.getStackTrace()); } return invokeResult; } @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) { try { InvokeRequest invokeRequest = readRequest(request); InvokeResult invokeResult = execute(invokeRequest); String result = objectMapper.writeValueAsString(invokeResult); response.setHeader("Content-Type", "application/json"); response.getWriter().write(result); response.getWriter().close(); } catch (Exception e) { try { response.getWriter().write(Throwables.getStackTraceAsString(e)); response.getWriter().close(); } catch (Exception ex) { log.error("fail to handle request"); } } } } public class InvokeRequest implements Serializable { private static final long serialVersionUID = 6162519478671749612L; /** * 測試方法所在的類 */ private Class<?> testClass; /** * 測試的方法名 */ private String methodName; }
編寫SpringDelegateRunner繼承SpringJUnit4ClassRunner
public class SpringDelegateRunner extends ModifiedSpringJUnit4ClassRunner { private ObjectMapper objectMapper = new ObjectMapper(); private final Class<?> testClass; private final Boolean DEBUG_MODE = true; public SpringDelegateRunner(Class<?> clazz) throws InitializationError { super(clazz); this.testClass = clazz; } /** * 遞交給遠(yuǎn)程執(zhí)行 * * @param method 執(zhí)行的方法 * @param notifier Runner通知 */ @Override protected void runChild(FrameworkMethod method, RunNotifier notifier) { Description description = describe(method); if (isIgnored(method)) { notifier.fireTestIgnored(description); return; } InvokeRequest invokeRequest = new InvokeRequest(); invokeRequest.setTestClass(method.getDeclaringClass()); invokeRequest.setMethodName(method.getName()); try { notifier.fireTestStarted(description); String json = objectMapper.writeValueAsString(invokeRequest); // http請求訪問服務(wù)器 String body = HttpRequest.post("http://127.0.0.1:" + DebugMaskUtil.getPort()).send(json).body(); if (StringUtils.isEmpty(body)) { notifier.fireTestFailure(new Failure(description, new RuntimeException("遠(yuǎn)程執(zhí)行失敗"))); } InvokeResult invokeResult = objectMapper.readValue(body, InvokeResult.class); Boolean success = invokeResult.getSuccess(); if (success) { notifier.fireTestFinished(description); } else { InvokeFailedException exception = invokeResult.getException(); if (exception.getAssertionError() != null) { notifier.fireTestFailure(new Failure(description, exception.getAssertionError())); } else { notifier.fireTestFailure(new Failure(description, invokeResult.getException())); } } } catch (Exception e) { notifier.fireTestFailure(new Failure(description, e)); } } }
@Slf4j @Controller @RequestMapping("junit") public class TestController { private ObjectMapper objectMapper = new ObjectMapper(); @Autowired private ApplicationContext applicationContext; private Map<String, Method> methodMap = new ConcurrentHashMap<>(); @PostMapping("/test") public void test(HttpServletRequest request, HttpServletResponse response){ int contentLength = request.getContentLength(); ServletInputStream inputStream; byte[] buffer = null; try { inputStream = request.getInputStream(); buffer = new byte[contentLength]; inputStream.read(buffer, 0, contentLength); inputStream.close(); } catch (IOException e) { e.printStackTrace(); } try { InvokeRequest invokeRequest = objectMapper.readValue(buffer, InvokeRequest.class); // InvokeRequest invokeRequest = JsonUtil.getObject(new String(buffer),InvokeRequest.class); InvokeResult execute = execute(invokeRequest); String result = objectMapper.writeValueAsString(execute); log.info("==================="+result); response.setHeader("Content-Type", "application/json"); response.getWriter().write(result); response.getWriter().close(); } catch (Exception e) { e.printStackTrace(); } } private void registerBeanOfType(Class<?> type) { BeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClassName(type.getName()); ((DefaultListableBeanFactory) (((GenericApplicationContext) applicationContext).getBeanFactory())) .registerBeanDefinition(type.getName(), beanDefinition); } private Method getMethod(Class clazz, String methodName) { String key = clazz.getCanonicalName() + ":" + methodName; Method md = null; if (methodMap.containsKey(key)) { md = methodMap.get(key); } else { Method[] methods = clazz.getMethods(); for (Method mth : methods) { if (mth.getName().equals(methodName)) { methodMap.putIfAbsent(key, mth); md = mth; break; } } } return md; } private InvokeResult execute(InvokeRequest invokeRequest) { Class<?> testClass = invokeRequest.getTestClass(); Object bean; try { bean = applicationContext.getBean(testClass.getName()); } catch (Exception e) { registerBeanOfType(testClass); bean = applicationContext.getBean(testClass.getName()); } InvokeResult invokeResult = new InvokeResult(); Method method = getMethod(testClass, invokeRequest.getMethodName()); try { method.invoke(bean); invokeResult.setSuccess(true); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { if (!(e instanceof InvocationTargetException) || !(((InvocationTargetException) e).getTargetException() instanceof AssertionError)) { log.error("fail to invoke code, cause: {}", Throwables.getStackTraceAsString(e)); } invokeResult.setSuccess(false); InvokeFailedException invokeFailedException = new InvokeFailedException(); invokeFailedException.setMessage(e.getMessage()); invokeFailedException.setStackTrace(e.getStackTrace()); // 由Assert拋出來的錯誤 if (e.getCause() instanceof AssertionError) { invokeFailedException.setAssertionError((AssertionError) e.getCause()); } invokeResult.setException(invokeFailedException); } catch (Exception e) { log.error("fail to invoke code, cause: {}", Throwables.getStackTraceAsString(e)); invokeResult.setSuccess(false); InvokeFailedException invokeFailedException = new InvokeFailedException(); invokeFailedException.setMessage(e.getMessage()); invokeFailedException.setStackTrace(e.getStackTrace()); } return invokeResult; } }
以上是“SpringBoot環(huán)境下junit單元測試速度優(yōu)化方式的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。