当前位置 : 首页 » 文章分类 :  开发  »  Spring-Test

Spring-Test

Spring/Spring Boot/JUnit 测试相关笔记

首先要分清几个概念:测试方法、测试类、测试集、测试运行器。

  • 测试方法就是用 @Test 注解的一些函数。
  • 测试类是包含一个或多个测试方法的一个 XxTest.java 文件
  • 测试集是一个suite, 可能包含多个测试类。
  • 测试运行器则决定了用什么方式偏好去运行这些测试集/类/方法。

Spring Boot 模块独立测试

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层

@WebMvcTest 测试web层,controller层,不包括service层。

@WebMvcTest 注解主要用于controller层测试,只覆盖应用程序的controller层,HTTP请求和响应是Mock出来的,因此不会创建真正的连接。因此需要创建 MockMvc bean进行模拟接口调用。
如果Controller层对Service层中的其他bean有依赖关系,那么需要使用Mock提供所需的依赖项。
WebMvcTest要快得多,因为我们只加载了应用程序的一小部分。


@DataJpaTest 测试 JPA

@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

@JdbcTest 测试jdbc


@DataMongoTest 测试mongo

@DataMongoTest 测试mongo

@DataMongoTest 注解会禁用 spring boot 的其他自动配置,只保留 mongo 测试相关的。


@JsonTest 测试json

@JsonTest 测试json序列化、反序列化


@RestClientTest 测试http客户端

@RestClientTest 测试http客户端


@SpringBootTest 测试springboot应用

@SpringBootTest 测试spring boot应用,各种service层。


@ActiveProfiles 指定profile

可以使用 @ActiveProfiles 注解指定 profile

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class SomeTestClass {
    //...
}

还可以传数组 @ActiveProfiles({"profile1", "profile2"})


@Transactional 测试数据自动回滚

可以实现再springboot中使用junit编写单元测试,并且测试结果不影响数据库。

@Transactional 表示该方法整体为一个事务,可以用在测试类上表示所有测试方法都回滚,或具体的 @Test 方法上。
@Rollback 表示事务执行完回滚,支持传入一个参数value,默认true即回滚,false不回滚。

springboot中junit回滚
https://www.jianshu.com/p/d9d0abf317c0


使用 MockMvc 测试 Spring MVC Controller

用到的注解:
@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

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


SpringBoot 单元测试

1、要让一个普通类变成一个单元测试类只需要在类名上加入 @SpringBootTest@RunWith(SpringRunner.class) 两个注释即可。
2、在测试方法上加上 @Test 注释。

Spring Boot 单元测试详解+实战教程
https://www.cnblogs.com/javastack/p/9150408.html


@SpringBootTest

@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

Unable to find a @SpringBootConfiguration

@SpringBootTest 注解的测试类执行报错:

java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test
Suppressed: java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test at org.springframework.util.Assert.state(Assert.java:73)

原因:
@SpringBootTest 注解的测试类找不到 @SpringBootConfiguration 应用主类(测试主类 和 src/main/java 应用主类都找不到)

解决:
方法1、Spring 会先在当前目录找 @SpringBootConfiguration 应用主类,然后根据包目录结构向上级依次查找。
出问题的目录结构有问题,应用主类在 com.masikkk 包中,但测试类没放到任何包中,只在 src/test/java 中,将测试类放到和应用主类相同的包或子包中即可
注意:没必要创建一个测试主类 TestApplication,测试类能找到 src/main/java 同包中的主类即可

@SpringBootApplication
public class TestApplication {
}

方法2、如果确实无法移动测试类的包结构,可以通过 @SpringBootTest(classes = MyWebApplication.class) 的方式告诉测试类去哪里找应用主类。


@TestPropertySource 设置System Property

@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


@ClassRule 和 @Rule

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

@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


@Test

在junit3中,是通过对测试类和测试方法的命名来确定是否是测试,且所有的测试类必须继承junit的测试基类。在junit4中,定义一个 测试方法变得简单很多,只需要在方法前加上@Test就行了。

注意:测试方法必须是public void,即公共、无返回数据。可以抛出异常。

junit常用注解详细说明
http://www.cnblogs.com/tobey/p/4837495.html


@ContextConfiguration

@ContextConfiguration 注解用于指定 spring 配置文件所在的路径,有以下几个常用的属性:

locations 配置文件路径

locations 可以通过该属性手工指定 Spring 配置文件所在的位置,可以指定一个或多个 Spring 配置文件。如下所示:

@ContextConfiguration(locations = {"xx/yy/beans1.xml","xx/yy/beans2.xml"})
@ContextConfiguration(locations = "classpath*:spring-ctx-*.xml")

inheritLocations 是否继承父类配置

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 指定配置类

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


Spring单元测试禁用consul

问题:
本地不启动 consul 服务的情况下,跑单测报错连不上默认的 consul 地址 localhost:8500

2022-09-14 10:24:37.529 ERROR 12840 --- [           main] o.s.test.context.TestContextManager      : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@6e9a5ed8] to prepare test instance [com.masikkk.MyJpaServiceTest@6c101cc1]

java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132) ~[spring-test-5.3.8.jar:5.3.8]
Caused by: org.springframework.cloud.consul.config.ConsulPropertySources$PropertySourceNotFoundException: com.ecwid.consul.transport.TransportException: org.apache.http.conn.HttpHostConnectException: Connect to localhost:8500 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused (Connection refused)
    at org.springframework.cloud.consul.config.ConsulPropertySources.createPropertySource(ConsulPropertySources.java:143) ~[spring-cloud-consul-config-3.0.4.jar:3.0.4]
Caused by: com.ecwid.consul.transport.TransportException: org.apache.http.conn.HttpHostConnectException: Connect to localhost:8500 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused (Connection refused)
    at com.ecwid.consul.transport.AbstractHttpTransport.executeRequest(AbstractHttpTransport.java:83) ~[consul-api-1.4.5.jar:na]
Caused by: org.apache.http.conn.HttpHostConnectException: Connect to localhost:8500 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused (Connection refused)
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:156) ~[httpclient-4.5.13.jar:4.5.13]

解决:
单测中禁用 consul

@TestPropertySource(properties = {"spring.cloud.consul.enabled=false"})
@SpringBootTest
public abstract class BaseTest {
}

https://stackoverflow.com/questions/39622363/spring-consul-disable-for-unit-tests


AopTestUtils.getUltimateTargetObject() 获取被代理的对象


上一篇 Nginx

下一篇 Hexo博客(23)弃用七牛云图床改为git仓库图床

阅读
评论
4.1k
阅读预计17分钟
创建日期 2018-10-09
修改日期 2022-09-14
类别

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论