用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官方文檔