欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot環(huán)境下junit單元測試速度優(yōu)化方式

 更新時(shí)間:2021年09月03日 09:56:15   作者:安安靜靜寫bug  
這篇文章主要介紹了SpringBoot環(huán)境下junit單元測試速度優(yōu)化方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

1、提高單元測試效率

背景

在項(xiàng)目提測前,自己需要對代碼邏輯進(jìn)行驗(yàn)證,所以單元測試必不可少。

但是現(xiàn)在的java項(xiàng)目幾乎都是基于SpringBoot系列開發(fā)的,所以在進(jìn)行單元測試時(shí),執(zhí)行一個(gè)測試類就要啟動(dòng)springboot項(xiàng)目,加載上下文數(shù)據(jù),每次執(zhí)行一次測試都要再重新加載上下文環(huán)境,這樣就會很麻煩,浪費(fèi)時(shí)間;在一次項(xiàng)目中,我們使用自己的技術(shù)框架進(jìn)行開發(fā),每次單元測試時(shí)都要初始化很多數(shù)據(jù)(例如根據(jù)數(shù)據(jù)模型建立表,加載依賴其它模塊的類),這樣導(dǎo)致每一次單元測試時(shí)都會花3-5分鐘時(shí)間(MacOs 四核Intel Core i5 內(nèi)存:16g),所以很有必要優(yōu)化單元測試效率,節(jié)約開發(fā)時(shí)間。

2、單元測試如何執(zhí)行

首先要優(yōu)化單元測試,那要知道單元測試是怎樣執(zhí)行的

引入相關(guān)測試的maven依賴,例如junit,之后在測試方法加上@Test注解即可,在springboot項(xiàng)目測試中還需要在測試類加上@RunWith注解 然后允許需要測試的方法即可

補(bǔ)充說明

  • @RunWith 就是一個(gè)運(yùn)行器
  • @RunWith(JUnit4.class) 就是指用JUnit4來運(yùn)行
  • @RunWith(SpringJUnit4ClassRunner.class),讓測試運(yùn)行于Spring測試環(huán)境
  • @RunWith(Suite.class) 的話就是一套測試集合,
  • @ContextConfiguration Spring整合JUnit4測試時(shí),使用注解引入多個(gè)配置文件@RunWith

SpringBoot環(huán)境下單元測試一般是加@RunWith(SpringJUnit4ClassRunner.class)注解,SpringJUnit4ClassRunner繼承BlockJUnit4ClassRunner類,然后在測試方式時(shí)會執(zhí)行SpringJUnit4ClassRunner類的run方法(重寫了BlockJUnit4ClassRunner的run方法),run方法主要是初始化spring環(huán)境數(shù)據(jù),與執(zhí)行測試方法

3、項(xiàng)目中使用

在我們項(xiàng)目中,是通過一個(gè)RewriteSpringJUnit4ClassRunner類繼承SpringJUnit4ClassRunner,然后@RunWith(RewriteSpringJUnit4ClassRunner.class)來初始化我們框架中需要的數(shù)據(jù),

RewriteSpringJUnit4ClassRunner里面是通過重寫withBefores方法,在withBefores方法中去初始化數(shù)據(jù)的,之后通過run方法最后代理執(zhí)行測試方法

4、優(yōu)化單測思路

通過上面說明,可以知道每次測試一個(gè)方法都要初始化springboot環(huán)境與加載自己框架的數(shù)據(jù),所以有沒有一種方式可以只需要初始化 一次數(shù)據(jù),就可以反復(fù)運(yùn)行測試的方法呢?

思路

首先每一次單測都需要重新加載數(shù)據(jù),跑完一次程序就結(jié)束了,所以每次測試方法時(shí)都要重新加載數(shù)據(jù),

如果只需要啟動(dòng)一次把環(huán)境數(shù)據(jù)都加載了,然后之后都單元測試方法都使用這個(gè)環(huán)境呢那不就能解決這個(gè)問題么。

我們是不是可以搞一個(gè)服務(wù)器,把基礎(chǔ)環(huán)境與數(shù)據(jù)都加載進(jìn)去,然后每次執(zhí)行單元測試方法時(shí),通過服務(wù)器代理去執(zhí)行這個(gè)方法,不就可以了嗎

5、實(shí)現(xiàn)方式

首先我們可以用springboot的方式啟動(dòng)一個(gè)服務(wù),通常使用的內(nèi)置tomcat作為服務(wù)啟,之后暴露一個(gè)http接口,入?yún)樾枰獔?zhí)行的類和方法,然后通過反射去執(zhí)行這個(gè)方法;還可以通過啟動(dòng)jetty服務(wù),通過jetty提供的handler處理器就可以處理請求,jetty相對于tomcat處理請求更加方便

服務(wù)是有了,那怎樣將單元測試方法代理給服務(wù)器呢?前面提到過,通過@RunWith注入的類,在單元測試方法運(yùn)行時(shí)會執(zhí)行@RunWith注入的類相應(yīng)的方法,所以我們可以在@RunWith注入的類里面做文章,拿到測試類與方法,然后通過http訪問服務(wù)器,然后服務(wù)器去代理執(zhí)行測試方法

6、編碼實(shí)現(xiàn)

下面將通過兩種不同方式實(shí)現(xiàn),以Jetty為服務(wù)器啟動(dòng),與以Tomcat為服務(wù)器啟動(dòng)

6.1 Jetty作為服務(wù)啟動(dòng)

首先編寫服務(wù)啟動(dòng)類,并在spring容器準(zhǔn)備好后加載我們公司框架相關(guān)數(shù)據(jù),這里使用jetty作為服務(wù)器,下面代碼是核心方法

// 只能寫在測試目錄下,因?yàn)閷懺趹?yīng)用程序目錄下在序列化時(shí),找不到測試目錄下的類-》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拋出來的錯(cuò)誤
            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));
        }
        }
    }

6.2 Tomcat作為容器啟動(dòng)

@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拋出來的錯(cuò)誤
            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;
    }
}

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java Web項(xiàng)目部署在Tomcat運(yùn)行出錯(cuò)與解決方法示例

    Java Web項(xiàng)目部署在Tomcat運(yùn)行出錯(cuò)與解決方法示例

    這篇文章主要介紹了Java Web項(xiàng)目部署在Tomcat運(yùn)行出錯(cuò)與解決方法,結(jié)合具體實(shí)例形式分析了Java Web項(xiàng)目部署在Tomcat過程中由于xml配置文件導(dǎo)致的錯(cuò)誤問題常見提示與解決方法,需要的朋友可以參考下
    2017-03-03
  • Java String類用法詳解

    Java String類用法詳解

    今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著Java String類用法展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • Java線程中sleep和wait的區(qū)別詳細(xì)介紹

    Java線程中sleep和wait的區(qū)別詳細(xì)介紹

    Java中的多線程是一種搶占式的機(jī)制,而不是分時(shí)機(jī)制。搶占式的機(jī)制是有多個(gè)線程處于可運(yùn)行狀態(tài),但是只有一個(gè)線程在運(yùn)行
    2012-11-11
  • 關(guān)于@ResponseBody 默認(rèn)輸出的誤區(qū)的解答

    關(guān)于@ResponseBody 默認(rèn)輸出的誤區(qū)的解答

    這篇文章主要介紹了關(guān)于@ResponseBody 默認(rèn)輸出的誤區(qū)的解答,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • 詳解SpringBoot的jar為什么可以直接運(yùn)行

    詳解SpringBoot的jar為什么可以直接運(yùn)行

    SpringBoot提供了一個(gè)插件spring-boot-maven-plugin用于把程序打包成一個(gè)可執(zhí)行的jar包,本文給大家介紹了為什么SpringBoot的jar可以直接運(yùn)行,文中有相關(guān)的代碼示例供大家參考,感興趣的朋友可以參考下
    2024-02-02
  • java異常和錯(cuò)誤類總結(jié)(必看篇)

    java異常和錯(cuò)誤類總結(jié)(必看篇)

    下面小編就為大家?guī)硪黄猨ava異常和錯(cuò)誤類總結(jié)(必看篇)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-09-09
  • Spring使用AspectJ的注解式實(shí)現(xiàn)AOP面向切面編程

    Spring使用AspectJ的注解式實(shí)現(xiàn)AOP面向切面編程

    這篇文章主要介紹了Spring使用AspectJ的注解式實(shí)現(xiàn)AOP面向切面編程的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Java開發(fā)必會的Linux命令

    Java開發(fā)必會的Linux命令

    這篇文章主要介紹了Java開發(fā)必會的Linux命令,幫助大家更好地進(jìn)行java開發(fā),感興趣的小伙伴們可以參考一下
    2015-12-12
  • Java中wait與sleep的區(qū)別講解(wait有參及無參區(qū)別)

    Java中wait與sleep的區(qū)別講解(wait有參及無參區(qū)別)

    這篇文章主要介紹了Java中wait與sleep的講解(wait有參及無參區(qū)別),通過代碼介紹了wait()?與wait(?long?timeout?)?區(qū)別,wait(0)?與?sleep(0)區(qū)別,需要的朋友可以參考下
    2022-04-04
  • 一篇文章帶你了解Java基礎(chǔ)-抽象

    一篇文章帶你了解Java基礎(chǔ)-抽象

    這篇文章主要給大家介紹了關(guān)于Java抽象定義以及舉例的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-08-08

最新評論