Java 單元測試基礎封裝

pom 文件導入 jar 包依賴

<!-- 模擬數據庫時使用 -->
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.200</version>
    <scope>test</scope>
</dependency>
<!-- 需要啓動 redis 服務時使用 -->
<!-- https://mvnrepository.com/artifact/ai.grakn/redis-mock -->
<dependency>
    <groupId>ai.grakn</groupId>
    <artifactId>redis-mock</artifactId>
    <version>0.1.6</version>
    <scope>test</scope>
</dependency>
<!-- 需要使用 zookeeper server 時使用 -->
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-test -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-test</artifactId>
    <version>5.0.0</version>
    <scope>test</scope>
</dependency>

編寫 H2 數據庫工具類

工具類主要用來執行 sql 語句文件,初始化腳本請在 配置文件中配置 schema

import lombok.extern.slf4j.Slf4j;
import org.h2.tools.RunScript;

import java.io.FileReader;
import java.io.Reader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * insert description here
 *
 * @author Showa.L
 * @since 2020/6/17 17:16
 */
@Slf4j
public class H2Utils {

    private static Connection CONNECTION = null;

    static {
        try {
            // 加載驅動
            Class.forName("org.h2.Driver");
        } catch (ClassNotFoundException ex) {
            log.error("get driver error", ex);
        }
    }

    private static void setupConn(String url) throws SQLException {
        if (null == CONNECTION || CONNECTION.isClosed()) {
            // 獲取鏈接
            CONNECTION = DriverManager.getConnection(url, "admin", "admin123");
        }
    }

    private static Statement getStatement() throws SQLException {
        if (null == CONNECTION || CONNECTION.isClosed()) {
            SQLException ex = new SQLException("No valid database connection!");
            throw ex;
        }

        return CONNECTION.createStatement();
    }

    /**
     * 關閉工具類的使用
     */
    public static void close() {
        try {
            CONNECTION.close();
        } catch (SQLException throwables) {
            log.error("run script error", throwables);
        }
    }

    /**
     * 開啓工具類的使用
     */
    public static void start(String db) {
        try {
            setupConn(db);
        } catch (SQLException throwables) {
            log.error("run script error", throwables);
        }
    }

    /**
     * 執行 sql 語句文件
     */
    public static void runScript(String filename) {
        String realPath = H2Utils.class.getResource(filename).getPath();
        try (Reader reader = new FileReader(realPath)) {
            RunScript.execute(CONNECTION, reader);
        } catch (Exception e) {
            log.error("run script error", e);
        }
    }

}

Spring Boot 容器模擬程序啓動基類——ApplicationTests

一些需要啓動整個環境的測試則需要繼承該類

/**
 * insert description here
 *
 * @author Showa.L
 * @since 2020/6/23 16:33
 */
@Slf4j
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ApplicationTests {

    // 測試完成後停止資源
    // redis 客戶端,未配置則不需要
    private static LettuceConnectionFactory LETTUCE_C_C;

    @Autowired(required = false)
    private LettuceConnectionFactory lettuceConnectionFactory;

    @Autowired
    protected MockMvc mockMvc;

    static {
        ServersUtils.startRedisServer();
        H2Utils.start("jdbc:h2:mem:db_tests;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE");

        Runtime.getRuntime().addShutdownHook(new Thread("xx-shutdown-hook") {
            @Override
            public void run() {
                //資源釋放動作
                destory();
            }
        });
    }

    @PostConstruct
    private void init() {
        LETTUCE_C_C = lettuceConnectionFactory;
    }

    static void destory() {
        // H2Utils stop
        H2Utils.close();

        // 銷燬 Redis 客戶端,主要是重連機制引發的程序無法關閉的問題
        if (LETTUCE_C_C != null)
            LETTUCE_C_C.destroy();

        ServersUtils.stopRedisServer();
    }

}

模擬服務啓動工具類——ServiceUtils

一些集成服務模擬:redis,zookeeper

import ai.grakn.redismock.RedisServer;
import lombok.extern.slf4j.Slf4j;

/**
 * insert description here
 *
 * @author Showa.L
 * @since 2020/6/18 9:02
 */
@Slf4j
public class ServersUtils {

    private static RedisServer redisServer = null;

    public static void startRedisServer() {
        try {
            if (redisServer == null) {
                redisServer = RedisServer.newRedisServer();
                redisServer.start();
                log.info("start redis server: port={}", redisServer.getBindPort());
                System.setProperty("spring.redis.port", Integer.toString(redisServer.getBindPort()));
            }
        } catch (Exception e) {
            log.error("start redis server error", e);
            stopRedisServer();
        }
    }

    public static void stopRedisServer() {
        if (redisServer != null)
            redisServer.stop();
    }


    private static TestingServer zookeeperServer = null;

    public static void startZookeeperServer(Integer port) {
        try {
            if (zookeeperServer == null) {
                log.info("start zookeeper server: port={}", port);
                zookeeperServer = new TestingServer(port);
            }
        } catch (Exception e) {
            log.error("start zookeeper server error", e);
        }
    }

    public static void stopZookeeperServer() {
        try {
            if (zookeeperServer != null)
                zookeeperServer.close();
        } catch (IOException e) {
            log.error("stop zookeeper server error", e);
        }
    }

}

Controller 測試基類——BaseControllerTest

用來 mock 測試 api 接口

import com.google.gson.Gson;
import com.sdstc.OrderApplicationTests;
import com.sdstc.core.utils.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import java.util.Map;

/**
 * insert description here
 *
 * @author Showa.L
 * @since 2020/6/19 9:30
 */
public class BaseControllerTests extends ApplicationTests {

    protected ResultActions get(String path, HttpHeaders httpHeaders, Map<String, String> params) throws Exception {
        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(path);
        if (httpHeaders != null)
            requestBuilder.headers(httpHeaders);

        if (params != null)
            params.forEach(requestBuilder::param);

        // 模擬 track_code。針對 RequestInfoForwardInterceptor 攔截引發的問題
        requestBuilder.header("track_code", StringUtils.randomIntString(12));

        return mockMvc.perform(requestBuilder);
    }

    protected ResultActions post(String path, HttpHeaders httpHeaders,
                                 Map<String, String> params,
                                 Object body) throws Exception {
        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
        if (httpHeaders != null)
            requestBuilder.headers(httpHeaders);

        if (params != null)
            params.forEach(requestBuilder::param);

        if (body != null)
            requestBuilder.content(new Gson().toJson(body));

        requestBuilder.contentType(MediaType.APPLICATION_JSON);
        // 模擬 track_code。針對 RequestInfoForwardInterceptor 攔截引發的問題
        requestBuilder.header("track_code", StringUtils.randomIntString(12));

        return mockMvc.perform(requestBuilder);
    }

}

Service 測試基類——BaseServiceTests

測試 service 時只需要測試方法邏輯的準確性,不需要啓動整個容器,直接使用 mockito 測試

/**
 * insert description here
 *
 * @author Showa.L
 * @since 2020/6/18 16:58
 */
public class BaseServiceTests {

    @BeforeEach
    void initMockito() {
        MockitoAnnotations.initMocks(this);
    }

}

Dao 測試基類——BaseMapperTests

測試 dao 需要啓動 spring 容器,所以需要繼承 ApplicationTests
可以在這個基類中進行一些必要的準備

public class BaseMapperTests extends ApplicationTests {

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章