SpringBoot+Spock的熟悉之路(四):用Spock+H2對SpringBoot進行集成測試

前言

本來打算是把Spock的使用寫成一篇的,後來發現太長了而且結構比較冗雜,還是拆分出來比較好,而且這樣也比較好檢索。初次使用,如果有誤,請輕噴,並指出問題。我會及時糾正。
要使用Spock,必須要有Junit和Mock的基本概念和使用。建議在使用Spock之前先懂了解一下Junit和Mockitio
上一篇博客:《SpringBoot+Spock的熟悉之路(三):用Spock對SpringBoot進行單元測試》

環境

一定要注意版本的問題!一定要注意版本的問題!一定要注意版本的問題!

Tool Version
Intellij IDEA 2018.3 Ultimate
SpringBoot 2.0.1
Java 1.8
mybatis-spring-boot 2.0.1
Groovy 2.4.6

完整的依賴

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.0.1</version>
    </dependency>
    <!--服務器配置的是Oracle-->
    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc8</artifactId>
        <version>12.2.0.1</version>
    </dependency>
        
    <!---------------Spock必須的------------------->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
	    <groupId>org.spockframework</groupId>
	    <artifactId>spock-core</artifactId>
	    <version>1.3-groovy-2.4</version>
	    <scope>test</scope>
	</dependency>
	<dependency>
	    <groupId>org.spockframework</groupId>
	    <artifactId>spock-spring</artifactId>
	    <version>1.3-RC1-groovy-2.4</version>
	    <scope>test</scope>
	</dependency>
	<dependency>
	    <groupId>org.codehaus.groovy</groupId>
	    <artifactId>groovy-all</artifactId>
	    <version>2.4.6</version>
	</dependency>
	<!--------------  可選項  ----------------->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
	    <groupId>org.projectlombok</groupId>
	    <artifactId>lombok</artifactId>
	    <optional>true</optional>
    </dependency>
    <!----------------方便MockMvc的時候模擬數據--------------->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.51</version>
    </dependency>
     <!----------------H2數據庫支持--------------->
    <dependency>
	   <groupId>com.h2database</groupId>
	   <artifactId>h2</artifactId>
	   <version>1.4.197</version>
	</dependency>
</dependencies>

<build>
   <plugins>
       <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
       </plugin>
   </plugins>
   <!-------爲了能讓項目識別資源文件--------->
   <resources>
       <resource>
           <directory>src/main/resources</directory>
           <includes>
               <include>**/*.properties</include>
               <include>**/*.xml</include>
               <include>**/*.sql</include>
           </includes>
           <filtering>false</filtering>
       </resource>
       <resource>
           <directory>src/main/java</directory>
           <includes>
               <include>**/*.properties</include>
               <include>**/*.xml</include>
               <include>**/*.sql</include>
           </includes>
           <filtering>false</filtering>
       </resource>
   </resources>
</build>

事先準備

添加配置文件

關於集成測試,最好的辦法還是在man下的配置文件裏建一個application-qa.properties,將集成測試所要用到的數據庫連接等信息放進去。大概樣子類似於這樣
在這裏插入圖片描述
application.properties文件裏的內容如下

server.port= 9090

spring.redis.host=127.0.0.1
spring.redis.port=6379

spring.datasource.url=jdbc:oracle:thin:@//********:***/*****
spring.datasource.username=********
spring.datasource.password=********

# MyBatis配置
mybatis.config-location=classpath:mybatis.xml

application-qa.properties文件裏的內容如下

server.port=8080

#************H2  Begin****************
#db schema
#初始化數據庫中數據,可以沒有
spring.datasource.schema=classpath:db/schema.sql

#remote visit
spring.h2.console.settings.web-allow-others=true
#console url
spring.h2.console.path=/h2-console
#default true
spring.h2.console.enabled=true
spring.h2.console.settings.trace=true

#DB_CLOSE_ON_EXIT=FALSE的意思是,關閉了JVM後,數據不會刪除,DATABASE_TO_UPPER的意思是,讓數據庫對大小寫敏感
spring.datasource.url=jdbc:h2:file:C:/data/sample;DB_CLOSE_ON_EXIT=FALSE;DATABASE_TO_UPPER=false;MODE=Oracle
#driver default:org.h2.Driver
spring.datasource.driver-class-name=org.h2.Driver
#default sa
spring.datasource.username=sa
#default null
spring.datasource.password=
#************H2  End****************

schema.sql

CREATE TABLE DEMO(
DEMO_ID NUMBER PRIMARY KEY NOT NULL,
DEMO_STR VARCHAR2(100)
);

CREATE TABLE DEMO_DICTONARY(
DICTONARY VARCHAR2(100) PRIMARY KEY NOT NULL
);

INSERT INTO DEMO(DEMO_ID,DEMO_STR) VALUES(1,'str1');

數據準備

BaseController

package com.example.demo.base;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;

public abstract class BaseController {

    @Autowired
    protected RestTemplate restTemplate;

}

BaseService

package com.example.demo.base;

import com.example.demo.repository.BaseRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.PostConstruct;
import java.util.List;

public abstract class BaseService {

    //加一個init方法,模擬我們容器完全啓動之前會去數據庫裏查詢字典值等額外數據
    @PostConstruct
    public List<String> getDictionary(){
        return baseRepository.getDictionary();
    }

    @Autowired
    protected RedisTemplate<String, String> redisTemplate;

    @Autowired
    private BaseRepository baseRepository;

}

BaseRepository 和xml

package com.example.demo.repository;

import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface BaseRepository {

    List<String> getDictionary();

}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.repository.BaseRepository">

    <select id="getDictionary" resultType="java.lang.String">
        SELECT
          DICTONARY
        FROM
          DEMO_DICTONARY
    </select>
</mapper>

DemoController

package com.example.demo.controller;


import com.example.demo.base.BaseController;
import com.example.demo.entity.DemoEntity;
import com.example.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/demo")
public class DemoController extends BaseController {

    @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public DemoEntity getDemo(Integer demoId) {
        return demoService.getDemo(demoId);
    }

    @Autowired
    private DemoService demoService;
}

Service 和 ServiceImpl

package com.example.demo.service;

import com.example.demo.entity.DemoEntity;

public interface DemoService {

    DemoEntity getDemo(Integer demoId);

}

package com.example.demo.service.impl;

import com.example.demo.entity.DemoEntity;
import com.example.demo.repository.DemoRepository;
import com.example.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DemoServiceImpl extends BaseService implements DemoService {


    @Autowired
    private DemoRepository demoRepository;

    @Override
    public DemoEntity getDemo(Integer demoId) {
        return demoRepository.getDemo(demoId);
    }
}

Dao層及對應的Xml文件

package com.example.demo.repository;

import com.example.demo.entity.DemoEntity;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface DemoRepository {

    DemoEntity getDemo(@Param("demoId") Integer demoId);
    
    Integer createDemo(@Param("param")DemoEntity demoEntity);
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.repository.DemoRepository">
    <select id="getDemo" resultType="com.example.demo.entity.DemoEntity">
        SELECT
          DEMO_ID  demoId,
          DEMO_STR  demoStr
        FROM
          DEMO
        WHERE
          DEMO_ID = #{demoId,jdbcType=NUMERIC}
    </select>

    <insert id="createDemo">
        INSERT INTO
        DEMO(
          DEMO_ID,
          DEMO_STR
        )
        VALUES(
          #{param.demoId,jdbcType=NUMERIC},
          #{param.demoStr,jdbcType=VARCHAR}
        )
    </insert>
</mapper>

配置H2數據庫的嵌入模式

上一篇博客曾說過關於H2數據庫的內存模式。這裏簡單說一下嵌入式模式。
嵌入式模式有點像office裏的Access數據庫,將數據庫文件保存在本地的,也是隻需要添加H2的相關依賴和配置信息就可以正常使用。想要刪除數據庫只需要要將整個數據庫文件刪除即可。
我上面的配置文件中指定了數據庫文件的位置

spring.datasource.url=jdbc:h2:file:C:/data/sample;DB_CLOSE_ON_EXIT=FALSE;DATABASE_TO_UPPER=false;MODE=Oracle

在啓動SpringBoot容器之後,就可以在這個地址下面發現這麼一個文件。
在這裏插入圖片描述
由於其有實際的數據庫文件,我們可以用第三方軟件(DataGrip等)來連接它,也可以使用H2自帶的頁面端控制檯。
配置文件中有這麼一句

spring.h2.console.path=/h2-console

那麼只要啓動SpringBoot容器後,在頁面輸入127.0.0.1:端口號/h2-console
就可以看到這麼一個頁面
在這裏插入圖片描述
將我們的數據庫連接字符串輸進去點擊Connect後,就能進入功能頁面,可以看到我們在schema.sql中創建的表格,也可以在中間的輸入框內執行基本的sql語句
在這裏插入圖片描述

一個簡單的例子

對於集成測試,不需要去考慮用Mock或者其他的去解決相關的依賴,其要的就是從上到下所有流程全部走一遍,因此不需要像我上一篇單元測試的博客那樣整那麼多麻煩的註解出來,直接使用@SpringBootTest註解,如果test項目結構與main中的不完全匹配,可能需要添加@SpringBootTest(classes = DemoApplication.class)

package com.example.demo.Integration


import com.example.demo.service.DemoService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import spock.lang.Specification

@SpringBootTest
@ActiveProfiles("qa")
@AutoConfigureMockMvc
class IntegrationSpec extends Specification {

    @Autowired
    MockMvc mockMvc

    @Autowired
    private DemoService demoService

    def "get entity test"() {
        expect:
        demoService.getDemo(1) != null
    }
}

啓動一下後查看控制檯,發現了在BaseService中使用@PostConstruct註解過的,查詢字典值的方法,可以看出來確實啓動了整個容器,並且我們可以在代碼中隨意使用@Autowired,而不用像單測一樣到處去Mock和Stub
在這裏插入圖片描述
如果要模擬網頁傳過來的數據,目前比較好的辦法還是使用MockMvc。
新加一個測試類

def "mvc test"() {
    given:
    def demoId = 1
    when:
    String result = mockMvc.perform(MockMvcRequestBuilders.get("/demo").param("demoId", demoId.toString()))
    .andReturn().getResponse().getContentAsString()
    then:
    print result
    result != null

}

執行後結果如下所示
在這裏插入圖片描述

遇到的問題

本模塊主要是把自己踩過的一些比較大的坑給單獨列舉出來

The file is locked

在這裏插入圖片描述
按照官方文檔的說法,嵌入模式下的H2是不允許多條連接的。所以看一下是不是main的主類已經啓動,或者是測試類和主類用的是同一個數據庫連接字符串。或者改爲Server模式。(之後看找機會把Server模式怎麼搭建給補上)

啓動的時候報Table “XXX” already exists; SQL statement

正常情況下嵌入模式的數據庫只需要在第一次連接的時候執行初始化腳本即可,看一下自己的
spring.datasource.schema=classpath:db/schema.sql是否存在
如果不想去改配置文件,那麼最好在初始化腳本前加上DROP TABLE IF EXISTS tableName

參考資料

Spock in Java 慢慢愛上寫單元測試
spock-testing-exceptions-with-data-tables
Spock官方文檔
Spock開源GitHub
Difference between Mock / Stub / Spy in Spock test framework
Mocks Aren’t Stubs
@Mock/@InjectMocks for groovy - spock
Difference between Mock / Stub / Spy in Spock test framework
spock-subjects-collaborators-extension
mybatis-spring-boot-autoconfigure
H2官方文檔

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