本来打算是把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');
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;
}
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;
}
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>
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;
}
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);
}
}
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数据库的内存模式。这里简单说一下嵌入式模式。
嵌入式模式有点像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
}
本模块主要是把自己踩过的一些比较大的坑给单独列举出来
按照官方文档的说法,嵌入模式下的H2是不允许多条连接的。所以看一下是不是main的主类已经启动,或者是测试类和主类用的是同一个数据库连接字符串。或者改为Server模式。(之后看找机会把Server模式怎么搭建给补上)
正常情况下嵌入模式的数据库只需要在第一次连接的时候执行初始化脚本即可,看一下自己的
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官方文档