做一个稳健的开发,写一首漂亮的单元测试是少不了的
首先要分清几个概念:测试方法、测试类、测试集、测试运行器。
Spring Boot 支持各模块单独隔离测试,例如web, db。单独测试各模块时不需要启动整个spring上下文,通过禁用一些spring boot自动配置来实现。
Spring Boot Test Slices Overview and Usage
https://rieckpil.de/spring-boot-test-slices-overview-and-usage/
@WebMvcTest
测试web层,controller层,不包括service层。
@WebMvcTest 注解主要用于controller层测试,只覆盖应用程序的controller层,HTTP请求和响应是Mock出来的,因此不会创建真正的连接。因此需要创建 MockMvc bean进行模拟接口调用。
如果Controller层对Service层中的其他bean有依赖关系,那么需要使用Mock提供所需的依赖项。
WebMvcTest要快得多,因为我们只加载了应用程序的一小部分。
@DataJpaTest
测试jpa @Repository EntityManager TestEntityManager DataSource
@DataJpaTest
注解会禁用 spring boot 的其他自动配置,只保留 jpa 测试相关的。
默认情况下 @DataJpaTest
注解的测试类都是事务型的,测试方法结束后会回滚操作。
默认情况下 @DataJpaTest
会启动一个内存数据库,例如 H2 或 Derby,来代替其他数据库。配合使用 @AutoConfigureTestDatabase
注解来自动配置一个测试库。
如果想加载全部spring上下文,同时使用内存数据库,应该使用 @SpringBootTest
搭配 @AutoConfigureTestDatabase
注解来实现。
@DataJpaTest
注解的测试类中可以直接注入 TestEntityManager
来作为 EntityManager
使用。
如果想在 @DataJpaTest
外使用 TestEntityManager, 需要添加 @AutoConfigureTestEntityManager 注解
TestEntityManager NullPointerException
一开始注入的 TestEntityManager 一直是 null,后来加上 @RunWith(SpringRunner.class) 就好了
有 @DataJpaTest
而不启动 Spring 上下文时,可以直接注入 TestEntityManager 使用。
如果不使用 @DataJpaTest
而是启动Spring上下文的话,就没有 TestEntityManager 实例可注入了,需要改为注入 EntityManager
Test Your Spring Boot JPA Persistence Layer With @DataJpaTest
https://rieckpil.de/test-your-spring-boot-jpa-persistence-layer-with-datajpatest/
@JdbcTest
测试jdbc
@DataMongoTest
测试mongo
@DataMongoTest
注解会禁用 spring boot 的其他自动配置,只保留 mongo 测试相关的。
@JsonTest
测试json序列化、反序列化
@RestClientTest
测试http客户端
@SpringBootTest
测试spring boot应用,各种service层。
可以实现再springboot中使用junit编写单元测试,并且测试结果不影响数据库。
@Transactional 表示该方法整体为一个事务,可以用在测试类上表示所有测试方法都回滚,或具体的 @Test 方法上。
@Rollback 表示事务执行完回滚,支持传入一个参数value,默认true即回滚,false不回滚。
springboot中junit回滚
https://www.jianshu.com/p/d9d0abf317c0
用到的注解:
@RunWith(SpringJUnit4ClassRunner.class): 表示使用Spring Test组件进行单元测试;
@WebAppConfiguration: 使用这个Annotate会在跑单元测试的时候真实的启动一个web服务,然后开始调用Controller的Rest API,待单元测试跑完之后再将web服务停掉;
@ContextConfiguration: 指定Bean的配置文件信息,可以有多种方式,这个例子使用的是文件路径形式,如果有多个配置文件,可以将括号中的信息配置为一个字符串数组来表示;controller,component等都是使用注解,需要注解指定spring的配置文件,扫描相应的配置,将类初始化等。
@TransactionConfiguration(transactionManager=”transactionManager”,defaultRollback=true)配置事务的回滚,对数据库的增删改都会回滚,便于测试用例的循环利用
为什么要进行事务回滚:
1、测试过程对数据库的操作,会产生脏数据,影响我们数据的正确性
2、不方便循环测试,即假如这次我们将一个记录删除了,下次就无法再进行这个Junit测试了,因为该记录已经删除,将会报错。
3、如果不使用事务回滚,我们需要在代码中显式的对我们的增删改数据库操作进行恢复,将多很多和测试无关的代码
测试类基类
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
//这个必须使用junit4.9以上才有
@RunWith(SpringJUnit4ClassRunner.class)
//单元测试的时候真实的开启一个web服务
@WebAppConfiguration
//配置事务的回滚,对数据库的增删改都会回滚,便于测试用例的循环利用
@TransactionConfiguration(transactionManager="transactionManager",defaultRollback=true)
@Transactional
@ContextConfiguration(locations = {"classpath:spring.xml","classpath:spring-hibernate.xml"})
public class AbstractContextControllerTests {
@Autowired
protected WebApplicationContext wac;
}
具体测试类
package com.pengtu.gsj;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.hibernate.SessionFactory;
import org.junit.Before;
import org.junit.Test;
import org.owasp.esapi.ESAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import com.pengtu.gsj.controller.BannerController;
import com.pengtu.gsj.dao.UserDao;
import com.pengtu.gsj.entity.app.User;
import com.pengtu.gsj.service.UserService;
public class EsapiTest extends AbstractContextControllerTests{
private MockMvc mockMvc;
//该方法在每个方法执行之前都会执行一遍
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(new BannerController()).build();
}
/**
* perform:执行一个RequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理;
* get:声明发送一个get请求的方法。MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根据uri模板 和uri变量值得到一个GET请求方式的。另外提供了其他的请求的方法,如:post、put、delete等。
* param:添加request的参数,如上面发送请求的时候带上了了pcode = root的参数。假如使用需要发送json数据格式的时将不能使用这种 方式,可见后面被@ResponseBody注解参数的解决方法
* andExpect:添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确(对返回的数据进行的判断);
* andDo:添加ResultHandler结果处理器,比如调试时打印结果到控制台(对返回的数据进行的判断);
* andReturn:最后返回相应的MvcResult;然后进行自定义验证/进行下一步的异步处理(对返回的数据进行的判断)
* @throws Exception
*/
@Test
public void getAllBanners() throws Exception{
String responseString = mockMvc.perform(get("/banner/hello") //请求的url,请求的方法是get
.contentType(MediaType.APPLICATION_JSON) //数据的格式
.param("id","123456789") //添加参数
).andExpect(status().isOk()) //返回的状态是200
.andDo(print()) //打印出请求和相应的内容
.andReturn().getResponse().getContentAsString(); //将相应的数据转换为字符串
System.out.println("--------返回的json = " + responseString);
}
}
perform:执行一个RequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理;
get:声明发送一个get请求的方法。MockHttpServletRequestBuilder get(String urlTemplate, Object… urlVariables):根据uri模板和uri变量值得到一个GET请求方式的。另外提供了其他的请求的方法,如:post、put、delete等。
param:添加request的参数,如上面发送请求的时候带上了了pcode = root的参数。假如使用需要发送json数据格式的时将不能使用这种方式。
andExpect:添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确(对返回的数据进行的判断);
andDo:添加ResultHandler结果处理器,比如调试时打印结果到控制台(对返回的数据进行的判断);
andReturn:最后返回相应的MvcResult;然后进行自定义验证/进行下一步的异步处理(对返回的数据进行的判断)
使用MockMvc测试Spring mvc Controller
https://blog.csdn.net/zhang289202241/article/details/62042842
SpringRunner 是 SpringJUnit4ClassRunner 的别名,两者没有任何区别
What is the difference between SpringJUnit4ClassRunner and SpringRunner
https://stackoverflow.com/questions/47446529/what-is-the-difference-between-springjunit4classrunner-and-springrunner
Class SpringRunner
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/context/junit4/SpringRunner.html
1、要让一个普通类变成一个单元测试类只需要在类名上加入 @SpringBootTest
和 @RunWith(SpringRunner.class)
两个注释即可。
2、在测试方法上加上 @Test
注释。
Spring Boot 单元测试详解+实战教程
https://www.cnblogs.com/javastack/p/9150408.html
@SpringBootTest
为 springApplication 创建上下文并支持 SpringBoot 特性
@SpringBootTest
注解告诉 SpringBoot 去寻找一个主配置类(例如带有 @SpringBootApplication
的配置类),并使用它来启动 Spring 应用程序上下文。SpringBootTest 加载完整的应用程序并注入所有可能的bean,因此速度会很慢。
在这种情况下,不需要创建 MockMvc bean,可以直接通过 RestTemplate 进行请求测试(或者使用 TestRestTemplate )。
使用 @SpringBootTest 的 webEnvironment 属性定义运行环境:
Mock(默认): 加载 WebApplicationContext 并提供模拟的 web 环境 Servlet环境,使用此批注时,不会启动嵌入式服务器
RANDOM_PORT: 加载 WebServerApplicationContext 并提供真实的 web 环境,嵌入式服务器,监听端口是随机的
DEFINED_PORT: 加载 WebServerApplicationContext 并提供真实的 Web 环境,嵌入式服务器启动并监听定义的端口(来自 application.properties 或默认端口 8080)
NONE: 使用 SpringApplication 加载 ApplicationContext 但不提供任何Web环境
Spring Test单元测试
http://jianwl.com/2016/08/07/Spring-Test%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/
Annotation Type SpringBootTest
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/SpringBootTest.html
@TestPropertySource 可以用来覆盖掉来自于系统环境变量、Java系统属性、@PropertySource的属性。
同时@TestPropertySource(properties=…)优先级高于@TestPropertySource(locations=…)。
利用它我们可以很方便的在测试代码里微调、模拟配置(比如修改操作系统目录分隔符、数据源等)。
Spring、Spring Boot和TestNG测试指南 - @TestPropertySource
https://segmentfault.com/a/1190000010854607
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:whereever/context.xml")
@TestPropertySource(properties = {"myproperty = foo"})
public class TestWarSpringContext {
...
}
How to set environment variable or system property in spring tests?
https://stackoverflow.com/questions/11306951/how-to-set-environment-variable-or-system-property-in-spring-tests
junit中的 @ClassRule,可以在所有类方法开始前进行一些初始化调用,比如创建临时文件,
package com.jdriven;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
public class JUnitClassRuleTest {
@ClassRule
public static TemporaryFolder temporaryFolder = new TemporaryFolder();
public static File tempFile;
@BeforeClass
public static void createTempFile() throws IOException {
tempFile = temporaryFolder.newFile("tempFile.txt");
}
@Test
public void testJUnitClassRule_One() {
//Your test should go here, which uses tempFile
}
@Test
public void testJUnitClassRule_Two() {
//Your test should go here and uses the same tempFile
}
}
其中,@ClassRule中指定创建临时文件夹,这是在所有的测试方法前会创建文件夹,并且会在所有测试完成后,递归删除其下的子目录和子文件夹。
@Rule 是方法级别的,每个测试方法执行时都会调用被注解的Rule,而@ClassRule是类级别的,在执行一个测试类的时候只会调用一次被注解的Rule
junit中的@classrule,@rule
http://jackyrong.iteye.com/blog/2193451
@RunWith
指定JUnit使用的单元测试执行类,使用@RunWith注解可以改变JUnit的默认执行类
@Runwith放在测试类名之前,用来确定这个类怎么运行的。也可以不标注,会使用默认运行器。
常见的运行器有:
参数化运行器
@RunWith(Parameterized.class) 参数化运行器,配合@Parameters使用junit的参数化功能
测试集运行器
@RunWith(Suite.class)
@SuiteClasses({ATest.class,BTest.class,CTest.class})
测试集运行器配合使用测试集功能
junit4的默认运行器
@RunWith(JUnit4.class)
junit4的默认运行器
兼容junit3.8的运行器
@RunWith(JUnit38ClassRunner.class)
用于兼容junit3.8的运行器
SpringJUnit4ClassRunner
@RunWith(SpringJUnit4ClassRunner.class)集成了spring的一些功能
junit常用注解详细说明
http://www.cnblogs.com/tobey/p/4837495.html
使用RunWith注解改变JUnit的默认执行类,并实现自已的Listener
http://blog.csdn.net/fenglibing/article/details/8584602
在junit3中,是通过对测试类和测试方法的命名来确定是否是测试,且所有的测试类必须继承junit的测试基类。在junit4中,定义一个 测试方法变得简单很多,只需要在方法前加上@Test就行了。
注意:测试方法必须是public void,即公共、无返回数据。可以抛出异常。
junit常用注解详细说明
http://www.cnblogs.com/tobey/p/4837495.html
@ContextConfiguration
注解用于指定 spring 配置文件所在的路径,有以下几个常用的属性:
locations
可以通过该属性手工指定 Spring 配置文件所在的位置,可以指定一个或多个 Spring 配置文件。如下所示:
@ContextConfiguration(locations = {"xx/yy/beans1.xml","xx/yy/beans2.xml"})
@ContextConfiguration(locations = "classpath*:spring-ctx-*.xml")
inheritLocations
:是否要继承父测试用例类中的 Spring 配置文件,默认为 true。如下面的例子:
@ContextConfiguration(locations={"base-context.xml"})
public class BaseTest {
// ...
}
@ContextConfiguration(locations={"extended-context.xml"})
public class ExtendedTest extends BaseTest {
// ...
}
如果 inheritLocations 设置为 false,则 ExtendedTest 仅会使用 extended-context.xml 配置文件,否则将使用 base-context.xml 和 extended-context.xml 这两个配置文件。
classes 属性可指定一些 Configuration 配置类,例如:
@SpringBootTest
@ContextConfiguration(classes = MyConfiguration.class)
public class ConsulLockTest {
}
Spring基于注解TestContext 测试框架使用详解
http://blog.csdn.net/yaerfeng/article/details/25368447
Spring 注解学习手札(六) 测试
http://snowolf.iteye.com/blog/588351