洛阳高新区做网站公司,淘宝店采用哪些方法做网站推广,网络小说网站建设,网站和公众号的区别Java进击框架#xff1a;Spring-Test#xff08;六#xff09; 前言单元测试模拟对象 集成测试上下文管理和缓存事务管理集成测试的支持类执行SQL脚本WebTestClientMockMvc JDBC测试支持其它注释 前言
Spring团队提倡测试驱动开发(TDD)。Spring团队发现#xff0c;控制反转… Java进击框架Spring-Test六 前言单元测试模拟对象 集成测试上下文管理和缓存事务管理集成测试的支持类执行SQL脚本WebTestClientMockMvc JDBC测试支持其它注释 前言
Spring团队提倡测试驱动开发(TDD)。Spring团队发现控制反转(IoC)的正确使用确实使单元和集成测试变得更容易(因为类上setter方法和适当构造函数的存在使它们更容易在测试中连接在一起而不必设置服务定位器注册中心和类似的结构)。
Spring TestContext框架(位于org.springframework.test.context包中)提供了通用的、注释驱动的单元测试和集成测试支持与所使用的测试框架无关。TestContext框架还非常重视约定而不是配置使用合理的默认值您可以通过基于注释的配置来覆盖这些默认值。
该框架的核心由TestContextManager类和TestContext、TestExecutionListener和SmartContextLoader接口组成。 TestContextManager是Spring TestContext框架的主要入口点负责管理单个TestContext并且向每个注册的TestExecutionListener在明确定义的测试执行点比如执行测试方法之前、测试方法之后。 TestContext封装测试运行的上下文(不知道实际使用的测试框架)并为它所负责的测试实例提供上下文管理和缓存支持。 TestExecutionListener定义API用于对TestContextManager侦听器向其注册。
RunWith(SpringRunner.class)
ContextConfiguration(classes AppConfig.class)
public class MyServiceTest {Testpublic void test() throws Exception {TestContextManager testContextManager new TestContextManager(MyServiceTest.class);testContextManager.prepareTestInstance(this);TestContext testContext testContextManager.getTestContext();}
}RunWith 是 JUnit 框架提供的一个注解用于指定测试类运行时使用的运行器Runner。在 Spring 中通常会使用 SpringRunner 运行器来执行基于 Spring 的测试。它是 JUnit 4 的默认运行器在 JUnit 5 中称为 SpringJUnit4ClassRunner。 上述示例代码中我们创建了一个 TestContextManager 对象并调用 prepareTestInstance(this) 方法来准备测试实例和测试上下文。通过 getTestContext() 方法获取当前测试的 TestContext 对象我们可以使用它来操作测试上下文。
以下内容中会用的一些依赖 dependencygroupIdorg.springframework/groupIdartifactIdspring-test/artifactIdversion5.3.23/version/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion5.3.23/version/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-web/artifactIdversion5.3.23/version/dependency!--junit 4--dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/versionscopecompile/scope/dependency!--junit 5--dependencygroupIdorg.junit.jupiter/groupIdartifactIdjunit-jupiter/artifactIdversion5.8.2/versionscopetest/scope/dependencydependencygroupIdorg.easymock/groupIdartifactIdeasymock/artifactIdversion4.3/versionscopetest/scope/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-webflux/artifactIdversion2.4.0/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdversion2.4.0/version/dependency单元测试
与传统的J2EE / Java EE开发相比依赖注入应该使您的代码对容器的依赖性更小。组成应用程序的POJOs在JUnit或TestNG测试中应该是可测试的使用new操作器不带Spring或任何其他容器。如果您遵循Spring的架构建议那么您的代码库的干净分层和组件化有助于更容易的单元测试。例如您可以通过存根或模仿DAO或存储库接口来测试服务层对象而无需在运行单元测试时访问持久数据。
模拟对象
Spring包含许多专门用于mock的包。
环境
org.springframework.mock.env包包含了Environment和PropertySource抽象的模拟实现。MockEnvironment和MockPropertySource对于开发依赖于环境特定属性的代码的容器外测试非常有用。
下面讲解这两种的用法示例代码如下
public class MyServiceTest {Testpublic void testGetValue() {ConfigurableEnvironment mockEnv new MockEnvironment().withProperty(my.property, mocked value);MyService myService new MyService();myService.setConfigurableEnvironment(mockEnv);System.out.println(myService.getValue());//断言返回的值与预期值相匹配assertEquals(mocked value2, myService.getValue());}
}
public class MyService {private String value;private ConfigurableEnvironment configurableEnvironment;public void setConfigurableEnvironment(ConfigurableEnvironment configurableEnvironment) {this.configurableEnvironment configurableEnvironment;}public String getValue() {return configurableEnvironment.getProperty(my.property);}
}创建了一个名为 MyService 的简单服务类它依赖于环境变量或属性值。MyService 类中的 getValue() 方法使用了 Spring 的 ConfigurableEnvironment 对象来获取属性值。在测试中我们使用 MockEnvironment 来模拟环境配置并通过 setProperty() 方法设置了属性 my.property 的值为 mocked value。
RunWith(SpringRunner.class)
ContextConfiguration(classes AppConfig.class)
TestPropertySource(properties my.propertymocked value)
public class MyServiceTest {Autowiredprivate MyService myService;Testpublic void testGetValue() {String value myService.getValue();//断言返回的值与预期值相匹配assertEquals(mocked value, value);}
}
public class MyService {Value(${my.property})private String myProperty;public String getValue() {return myProperty;}
}
Configuration
public class AppConfig {Beanpublic MyService myService() {return new MyService();}
}通过 TestPropertySource 注解将 MockPropertySource 添加到测试环境中从而完成测试。需要注意的是TestPropertySource 注解会覆盖项目中已存在的属性源因此在设置虚拟属性值时要确保不会意外地覆盖了其他属性。
JNDI
JNDI 是 Java 平台提供的一种标准接口用于访问命名和目录服务。在开发应用程序时有时需要通过 JNDI 来获取外部资源例如数据库连接池、消息队列等。为了方便测试这些依赖于 JNDI 的代码可以使用 org.springframework.mock.jndi 工具包提供的模拟对象。从Spring Framework 5.2开始软件包被正式弃用取而代之的是来自第三方的完整解决方案例如Simple-JNDI。
Servlet API
org.springframework.mock.web包包含一套全面的Servlet API模拟对象这些对象对于测试web上下文、控制器和过滤器非常有用。这些模拟对象旨在与Spring的Web MVC框架一起使用通常比动态模拟对象(例如EasyMock)或替代的Servlet API模拟对象(例如模拟对象)。
示例代码如下
public class MyServiceTest {Testpublic void testAdd() {// 创建模拟对象CalculatorService mockService createMock(CalculatorService.class);// 设置模拟对象的期望行为expect(mockService.add(2, 3)).andReturn(5);// 将模拟对象注入到被测试类中Calculator calculator new Calculator(mockService);// 对受测方法进行测试replay(mockService);int result calculator.add(2, 3);// 验证期望行为是否正确响应verify(mockService);assertEquals(5, result);}
}
public interface CalculatorService {int add(int a, int b);
}
public class Calculator {private CalculatorService service;public Calculator(CalculatorService service) {this.service service;}public int add(int a, int b) {return service.add(a, b);}
}Spring MVC测试框架建立在模拟Servlet API对象的基础上为Spring MVC提供了一个集成测试框架。参考MockMvc。
Spring Web响应式
org.springframework.mock.http.server.reactive包包含了WebFlux应用中使用的ServerHttpRequest和ServerHttpResponse的模拟实现。org.springframework.mock.web.server包包含一个模拟ServerWebExchange它依赖于那些模拟请求和响应对象。
MockServerHttpRequest和MockServerHttpResponse都从相同的抽象基类扩展为特定于服务器的实现并与它们共享行为。
MockServerHttpRequest示例代码如下
public class MyServiceTest {Testpublic void testAdd() {// 创建一个GET请求MockServerHttpRequest request MockServerHttpRequest.get(/api/users).header(Content-Type, application/json).queryParam(page, 1).build();}
}MockServerHttpResponse示例代码如下
public class MyServiceTest {Testpublic void testAdd() throws UnsupportedEncodingException {// 创建一个MockHttpServletResponse对象MockHttpServletResponse response new MockHttpServletResponse();// 设置响应状态码response.setStatus(HttpServletResponse.SC_OK);// 设置响应头response.setHeader(Content-Type, application/json);// 设置响应体内容String responseBody {\message\: \Hello, world!\};response.setContentLength(responseBody.length());response.getWriter().write(responseBody);// 获取响应信息int statusCode response.getStatus();String contentType response.getHeader(Content-Type);String responseBody2 response.getContentAsString();}
}Spring包含了许多有助于单元测试的类。它们分为两类
通用测试工具
org.springframework.test.util软件包包含几个通用的工具用于单元和集成测试。
AopTestUtils是AOP相关实用方法的集合。您可以使用这些方法获得对隐藏在一个或多个Spring代理后面的底层目标对象的引用。
public class MyServiceTest {Testpublic void testAdd() {//获取代理对象背后的最终目标对象即被代理的真实对象。Object target AopTestUtils.getUltimateTargetObject(MyAspect.class);System.out.println(target);// 获取代理对象的目标对象即被代理的对象可能是最终目标对象或者另一个代理对象。Object target2 AopTestUtils.getTargetObject(MyAspect.class);System.out.println(target2);}
}ReflectionTestUtils是基于反射的实用方法的集合。您可以在需要更改常数值、设置private字段调用一个private的setter()方法或者调用一个private测试应用程序代码的用例时的配置或生命周期回调方法。
public class MyServiceTest {Testpublic void testAdd() {MyService myService new MyService();// 使用ReflectionTestUtils设置私有字段的值ReflectionTestUtils.setField(myService, value, hello world);// 使用ReflectionTestUtils调用私有方法并检查返回值Object actualValue ReflectionTestUtils.invokeMethod(myService, getValue);System.out.println(actualValue);}
}
public class MyService {private String value;private String getValue() { return value; }public void setValue(String value) { this.value value; }
}TestSocketUtils可用于在可用的随机端口上启动外部服务器的集成测试。然而这些实用程序不能保证给定端口的后续可用性因此是不可靠的。而不是使用TestSocketUtils要为服务器找到可用的本地端口建议您依靠服务器的能力在它选择或由操作系统分配的随机临时端口上启动。要与该服务器交互您应该查询服务器当前使用的端口。
Spring MVC测试实用程序
org.springframework.test.web包装包含ModelAndViewAssert您可以将它与JUnit、TestNG或任何其他处理Spring MVC的单元测试框架结合使用ModelAndView对象。
public class MyServiceTest {Testpublic void test() {ModelAndView modelAndView new ModelAndView(myView);//使用assertViewName方法断言视图名称是否为myView。assertViewName(modelAndView, myView);}
}集成测试
能够在不需要部署到应用服务器或连接到其他企业基础设施的情况下执行一些集成测试是很重要的。这样做可以让您测试以下内容 Spring IoC容器上下文的正确连接。 使用JDBC或ORM工具进行数据访问。这包括SQL语句的正确性、Hibernate查询、JPA实体映射等等。
Spring的集成测试支持有以下主要目标
管理测试之间的Spring IoC容器缓存。提供测试fixture实例的依赖注入。提供适合集成测试的事务管理。提供特定于spring的基类帮助开发人员编写集成测试。
上下文管理和缓存
Spring TestContext框架提供了一致的Spring加载ApplicationContext实例和WebApplicationContext实例以及这些上下文的缓存。支持加载上下文的缓存很重要因为启动时间会成为一个问题——不是因为Spring本身的开销而是因为Spring容器实例化的对象需要时间来实例化。例如一个包含50到100个Hibernate映射文件的项目可能需要10到20秒来加载映射文件在每个测试设备中运行每个测试之前产生的成本会导致整个测试运行变慢从而降低开发人员的工作效率。
默认情况下一旦加载配置的ApplicationContext将为每个测试重用。因此每个测试套件只产生一次设置成本并且随后的测试执行速度要快得多。在这种情况下“测试套件”一词意味着所有测试都在同一个JVM中运行——例如所有测试都是从给定项目或模块的Ant、Maven或Gradle构建中运行的。在不太可能的情况下测试破坏了应用程序上下文并需要重新加载(例如通过修改bean定义或应用程序对象的状态)TestContext框架可以配置为在执行下一个测试之前重新加载配置并重新构建应用程序上下文。
使用XML资源的上下文配置
使用TestContext框架的测试类不需要扩展任何特定的类或实现特定的接口来配置它们的应用程序上下文。相反通过声明ContextConfiguration类级别的注释。默认情况下则配置的ContextLoader确定如何从默认位置或默认配置类加载上下文。除了上下文资源位置和组件类应用程序上下文也可以通过应用程序上下文初始化器来配置。
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdbean idmyService classcom.example.MyService/bean
/beanspublic class MyService {
}
RunWith(value SpringJUnit4ClassRunner.class)
ContextConfiguration(locations classpath:dao.xml)
public class MyServiceTest {AutowiredMyService myService;Testpublic void test(){System.out.println(myService);}}如果您ContextConfiguration省略了注释中的locations和value这两个属性TestContext框架试图检测一个默认的XML资源位置。如果您的类被命名为com.example.MyServiceTest GenericXmlContextLoader加载应用程序上下文classpath:com/example/MyServiceTest-context.xml。 使用组件类进行上下文配置
你可以用ContextConfiguration注释加载组件类并用一个包含对组件类引用的数组来配置classes属性。 术语“组件类”可以指以下任何一种 用注释的类Configuration。一个组件(即一个用Component, Service, Repository或者其他原型注释)。一个符合JSR 330标准的类用jakarta.inject注释。任何包含以下内容的类Bean-方法。任何其他打算注册为Spring组件的类(例如ApplicationContext)潜在地利用了单个构造函数的自动装配而不使用Spring注释。 public class MyService {}
Configuration
public class AppConfig {Beanpublic MyService myService(){return new MyService();}
}
RunWith(value SpringRunner.class)
ContextConfiguration(classes AppConfig.class)
public class MyServiceTest {AutowiredMyService myService;Testpublic void test(){System.out.println(myService);}
}带有上下文初始化器的上下文配置
ContextConfiguration 注解中的 initializers 属性用于指定要在测试上下文加载之前执行的初始化回调对象ApplicationContextInitializer该数组包含对实现ApplicationContextInitializer。
public class MyApplicationContextInitializer implements ApplicationContextInitializerConfigurableApplicationContext {Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println(init);}
}
RunWith(value SpringRunner.class)
ContextConfiguration(classes AppConfig.class,initializers MyApplicationContextInitializer.class)
public class MyServiceTest {AutowiredMyService myService;Testpublic void test(){System.out.println(myService);}/** Output:* init* com.example.MyService76f2bbc1*/
}上下文配置继承
ContextConfiguration支持布尔值inheritLocations和inheritInitializers指示是否应该继承由超类声明的资源位置或组件类和上下文初始值设定项的属性。两个标志的默认值都是true设置为false时子类必须自行定义属性值否则会抛出 IllegalStateException 异常。
ContextConfiguration(locations classpath:MyServiceTest-context.xml)
public class BaseTest {
}
RunWith(value SpringRunner.class)
ContextConfiguration(inheritLocations true)
public class MyServiceTest extends BaseTest{AutowiredMyService myService;Testpublic void test(){System.out.println(myService);}/** Output:* com.example.MyService76f2bbc1*/
}环境配置文件的上下文配置
Spring框架对环境和概要文件(又名“bean定义概要文件”)的概念具有一流的支持并且可以配置集成测试来为各种测试场景激活特定的bean定义概要文件。这是通过用ActiveProfiles注释一个测试类并提供一个应该在为测试加载ApplicationContext时激活的配置文件列表来实现的。
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdbeans profiledevbean idmyService classcom.example.MyServiceproperty namevalue valuedev/property/bean/beansbeans profileprodbean idmyService classcom.example.MyServiceproperty namevalue valueprod/property/bean/beans
/beanspublic class MyService {private String value;public String getValue() { return value; }public void setValue(String value) { this.value value; }
}
RunWith(value SpringRunner.class)
ContextConfiguration
ActiveProfiles(value prod)
public class MyServiceTest{AutowiredMyService myService;Testpublic void test(){System.out.println(myService.getValue());}/** Output:* prod*/
}也可以使用注解的形式示例代码如下
Configuration
public class AppConfig {BeanProfile(dev)public MyService myServiceDev(){MyService myService new MyService();myService.setValue(dev);return myService;}BeanProfile(prod)public MyService myServiceProd(){MyService myService new MyService();myService.setValue(prod);return myService;}
}
RunWith(value SpringRunner.class)
ContextConfiguration(classes AppConfig.class)
ActiveProfiles(value prod)
public class MyServiceTest{AutowiredMyService myService;Testpublic void test(){System.out.println(myService.getValue());}/** Output:* prod*/
}带有测试属性源的上下文配置
您可以声明TestPropertySource注释来声明测试属性文件或内联属性的资源位置。 namehello RunWith(value SpringRunner.class)
TestPropertySource(locations /test.properties)
public class MyServiceTest{Value(${name})String name;Testpublic void test(){System.out.println(name);}/** Output:* hello*/
}通过使用TestPropertySource的properties属性可以以键值对的形式配置内联属性如下面的示例所示
RunWith(value SpringRunner.class)
TestPropertySource(locations /test.properties,properties {name:world})
public class MyServiceTest{Value(${name})String name;Testpublic void test(){System.out.println(name);}/** Output:* world*/
}支持的语法结构有keyvalue、key:value、key value。
如果TestPropertySource被声明为空批注(也就是说没有locations或者properties属性)如果带注释的测试类是com.example.MyTest对应的默认属性文件是classpath:com/example/MyTest.properties。如果无法检测到默认值则引发IllegalStateException被抛出。
具有动态属性源的上下文配置
从Spring Framework 5.2.5开始TestContext框架支持动态的属性通过DynamicPropertySource注释。
RunWith(value SpringRunner.class)
TestPropertySource(locations /test.properties,properties {name:world,nameworld2})
public class MyServiceTest{DynamicPropertySourcestatic void redisProperties(DynamicPropertyRegistry registry) {registry.add(name, ()-张山);}Value(${name})String name;Testpublic void test(){System.out.println(name);}/** Output:* 张山*/
}动态属性的优先级高于从TestPropertySource操作系统的环境、Java系统属性或由应用程序通过使用PropertySource或者以编程方式。
上下文缓存
一旦TestContext框架为一个测试加载了一个ApplicationContext(或WebApplicationContext)这个context就会被缓存并重用给所有在同一个测试套件中声明了相同唯一的context配置的后续测试。
Spring TestContext框架将应用程序上下文存储在静态缓存中。这意味着上下文实际上存储在一个静态变量中。换句话说如果测试在单独的进程中运行则在每次测试执行之间清除静态缓存这将有效地禁用缓存机制。
为了从缓存机制中获益所有测试必须在相同的进程或测试套件中运行。
上下文层次结构
当编写依赖加载弹簧的集成测试时ApplicationContext通常针对单个上下文进行测试就足够了。在测试中有时您需要创建多个嵌套的应用程序上下文以便模拟复杂的应用程序结构或依赖关系。ContextHierarchy 注解允许您按层次结构组织这些应用程序上下文。
RunWith(SpringJUnit4ClassRunner.class)
ContextHierarchy({ContextConfiguration(classpath:context1.xml),ContextConfiguration(classpath:context2.xml)
})
public class MyTest {// 测试内容
}我们使用 ContextHierarchy 注解来定义两个层次的应用程序上下文。第一个层次的上下文由 context1.xml 文件定义第二个层次的上下文由 context2.xml 文件定义。这意味着 context2.xml 中的 bean 可以访问 context1.xml 中定义的 bean。
通过使用 ContextHierarchy 注解您可以在测试中创建多个层次的应用程序上下文并且它们之间可以相互访问和共享 bean。这在某些复杂的测试场景下非常有用。
事务管理
在TestContext框架中事务由TransactionalTestExecutionListener管理它是默认配置的即使您没有在测试类上显式声明TestExecutionListeners。此外您必须在类或方法级别为您的测试声明Spring的Transactional注释。
TransactionalTestExecutionListener期望在Spring ApplicationContext中为测试定义一个PlatformTransactionManager bean 。如果在测试的ApplicationContext中有多个PlatformTransactionManager的实例你可以通过使用Transactional(myTxMgr)或Transactional(transactionManager myTxMgr)来声明一个限定符或者TransactionManagementConfigurer可以通过Configuration类来实现。
使用 XML 配置
tx:annotation-driven transaction-managertransactionManager /
bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManagerproperty namedataSource refdataSource /
/bean使用 Java 配置
Configuration
public class TransactionConfig {Autowiredprivate DataSource dataSource;Beanpublic PlatformTransactionManager transactionManager() {return new DataSourceTransactionManager(dataSource);}
}启用和禁用事务
用Transactional注释测试方法会导致测试在事务中运行默认情况下该事务在测试完成后自动回滚。如果用Transactional注释了测试类则该类层次结构中的每个测试方法都在事务中运行。没有使用Transactional注释的测试方法(在类或方法级别)不会在事务中运行。请注意Transactional不支持测试生命周期方法——例如用JUnit Jupiter的BeforeAll、BeforeEach等注释的方法。此外带有Transactional注释但将传播属性设置为NOT_SUPPORTED或NEVER的测试不会在事务中运行。
Transactional属性支持
属性支持测试管理的事务value和transactionManager是propagation仅仅Propagation.NOT_SUPPORTED和Propagation.NEVER受到支持isolation不timeout不readOnly不rollbackFor和rollbackForClassName否:使用TestTransaction.flagForRollback()代替noRollbackFor和noRollbackForClassName否:使用TestTransaction.flagForCommit()代替
事务回滚和提交行为
默认情况下测试事务将在测试完成后自动回滚但是事务性提交和回滚行为可以通过Commit和Rollback注释。
RunWith(SpringRunner.class)
SpringBootTest
Transactional
public class MyTest {Autowiredprivate UserRepository userRepository;TestCommitpublic void testCommit() {User user new User();user.setUsername(test);user.setPassword(123456);userRepository.save(user);assertThat(userRepository.findByUsername(test)).isNotNull();}TestRollbackpublic void testRollback() {User user new User();user.setUsername(test);user.setPassword(123456);userRepository.save(user);assertThat(userRepository.findByUsername(test)).isNotNull();}
}您可以在测试方法中、方法之前和方法之后使用TestTransaction来启动或结束当前测试管理的事务或者为回滚或提交配置当前测试管理的事务。只要启用了TransactionalTestExecutionListener对TestTransaction的支持就自动可用。
RunWith(SpringRunner.class)
public class MyTest {Autowiredprivate UserRepository userRepository;Testpublic void testCommit() {// 开启事务TestTransaction.start();try {User user new User();user.setUsername(test);user.setPassword(123456);userRepository.save(user);// 标记事务为回滚状态TestTransaction.flagForRollback();// 断言事务已标记为回滚状态assertTrue(TestTransaction.isFlaggedForRollback());// 结束事务并验证数据库状态TestTransaction.end();// 可以进行其他断言或验证操作} finally {// 重置事务状态确保下一个测试方法开始时的事务状态正常TestTransaction.flagForCommit();TestTransaction.end();}}
}有时您可能需要在事务性测试方法之前或之后但在事务性上下文之外运行某些代码TransactionalTestExecutionListener支持BeforeTransaction和AfterTransaction针对此类场景的注释。
RunWith(SpringRunner.class)
Transactional
public class MyTest {Autowiredprivate UserRepository userRepository;BeforeTransactionpublic void beforeTransaction() {// 在事务开启前操作}AfterTransactionpublic void afterTransaction() {// 在事务回滚或提交后操作}Testpublic void testSaveUser() {// 测试方法中省略具体测试逻辑}
}集成测试的支持类
Spring TestContext框架提供了几个abstract支持简化集成测试编写的类。这些基本测试类提供了测试框架中定义良好的钩子以及方便的实例变量和方法让您可以访问 ApplicationContext用于执行显式bean查找或测试上下文的整体状态。 一个JdbcTemplate用于执行SQL语句来查询数据库。您可以在执行与数据库相关的应用程序代码之前和之后使用这样的查询来确认数据库状态并且Spring确保这样的查询与应用程序代码在相同的事务范围内运行。当与ORM工具一起使用时一定要避免误报。 Spring TestContext框架提供了与JUnit 5中引入的JUnit Jupiter测试框架的完全集成。通过用注释测试类ExtendWith(SpringExtension.class)您可以实现标准的基于JUnit Jupiter的单元和集成测试例如支持加载应用程序上下文、测试实例的依赖注入、事务性测试方法执行等等。 ExtendWith(SpringExtension.class)
ContextConfiguration(classes { AppConfig.class })
public class MyTest {Autowiredprivate MyService myService;Testpublic void testMyMethod() {// 测试代码}
}执行SQL脚本
Spring提供了以下选项用于在集成测试方法中以编程方式执行SQL脚本。
org.springframework.jdbc.datasource.init.ScriptUtils提供了一组用于处理SQL脚本的静态实用工具方法主要供框架内部使用。org.springframework.jdbc.datasource.init.ResourceDatabasePopulator提供基于对象的API通过使用外部资源中定义的SQL脚本以编程方式填充、初始化或清理数据库。org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTestsorg.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests Testpublic void test() {// 创建 ResourceDatabasePopulator 实例ResourceDatabasePopulator populator new ResourceDatabasePopulator();// 添加 SQL 脚本文件资源populator.addScript(new ClassPathResource(data.sql));// 执行填充操作populator.execute(dataSource);}除了上述以编程方式运行SQL脚本的机制之外您可以声明Sql测试类或测试方法上的注释用于配置单个SQL语句或SQL脚本的资源路径这些SQL脚本应在集成测试方法之前或之后针对给定数据库运行。
RunWith(value SpringRunner.class)
ContextConfiguration(classes AppConfig.class)
Sql(scripts create_table.sql)
public class MyServiceTest {TestSql(scripts data.sql,statements 需要执行的sql语句,executionPhase Sql.ExecutionPhase.BEFORE_TEST_METHOD)public void test() {}
}statements 属性添加一条新的sql。executionPhase属性用于指定 SQL 执行的阶段默认情况下executionPhase 的值为 BEFORE_TEST_METHOD即在每个测试方法执行之前执行 SQL 脚本。除此之外还有一些其它值AFTER_TEST_METHOD测试方法执行之后执行BEFORE_TEST_CLASS测试类的所有测试方法之前执行AFTER_TEST_CLASS测试类的所有测试方法之后执行。
如果没有指定SQL脚本或语句则尝试检测default脚本取决于位置Sql已声明。如果无法检测到默认值则引发IllegalStateException被抛出。 类级声明如果带注释的测试类是com.example.MyTest相应的默认脚本是classpath:com/example/MyTest.sql。 方法级声明如果带注释的测试方法被命名为testMethod()并且在类中定义com.example.MyTest相应的默认脚本是classpath:com/example/MyTest.testMethod.sql。 sql分组
SqlGroup 注解可以用于对多个 Sql 注解进行分组从而方便地在测试类或测试方法上进行统一配置。
RunWith(value SpringRunner.class)
ContextConfiguration(classes AppConfig.class)
SqlGroup({Sql(scripts create_table.sql,config SqlConfig(commentPrefix #)),Sql(scripts drop_table.sql)})
public class MyServiceTest {
}解释sql
SqlConfig 是一个用于配置 Sql 注解行为的注解。它可以定义一些与 SQL 脚本执行相关的参数例如分隔符、错误处理等。 SqlConfig 注解不限于以下属性 separator指定 SQL 脚本中的语句分隔符默认为 ;。你可以根据自己的需要将其设置为其他分隔符例如 $ 或 $$。commentPrefix指定 SQL 脚本中的注释前缀默认为 --。如果你的 SQL 脚本使用了其他注释前缀可以在此处进行配置。errorMode指定 SQL 脚本执行过程中遇到错误时的处理方式默认为 ErrorMode.FAIL_ON_ERROR。可以选择的处理方式包括 FAIL_ON_ERROR、CONTINUE_ON_ERROR 和 HALT_ON_ERROR分别表示在遇到错误时抛出异常、继续执行脚本但记录错误、或者停止执行脚本并记录错误。 RunWith(value SpringRunner.class)
ContextConfiguration(classes AppConfig.class)
Sql(scripts create_table.sql,config SqlConfig(commentPrefix #))
public class MyServiceTest {
}合并sql
从Spring Framework 5.2开始可以合并方法级Sql具有类级声明的声明使用SqlMergeMode 注解。
RunWith(value SpringRunner.class)
ContextConfiguration(classes AppConfig.class)
SqlMergeMode(value SqlMergeMode.MergeMode.MERGE)
public class MyServiceTest {
}要启用Sql合并请用SqlMergeMode(MERGE)注释测试类或测试方法。要禁用特定测试方法(或特定测试子类)的合并您可以通过SqlMergeMode(OVERRIDE)切换回默认模式。
WebTestClient
WebTestClient是为测试服务器应用程序而设计的HTTP客户端。它包裹着春天的网络客户端并使用它来执行请求但公开了用于验证响应的测试外观。WebTestClient可用于执行端到端HTTP测试。它还可以通过模拟服务器请求和响应对象在没有运行服务器的情况下测试Spring MVC和Spring WebFlux应用程序。
绑定到控制器
这种设置允许您通过模拟请求和响应对象测试特定的控制器而无需运行服务器。
对于WebFlux应用程序使用下面的代码来加载与WebFlux Java配置注册给定的控制器并创建一个WebHandler链要处理请求
WebTestClient client WebTestClient.bindToController(new TestController()).build();对于Spring MVC使用下面的代码StandaloneMockMvcBuilder加载等效于WebMvc Java配置注册给定的控制器并创建MockMvc要处理请求
WebTestClient client MockMvcWebTestClient.bindToController(new TestController()).build();你还可以绑定到ApplicationContext
RunWith(SpringRunner.class)
WebAppConfiguration
public class MyServiceTest {Autowiredprivate WebApplicationContext webApplicationContext;Testpublic void test() {WebTestClient client MockMvcWebTestClient.bindToApplicationContext(webApplicationContext).build();}
}如果你使用ApplicationContext进行绑定推荐使用RunWith(SpringRunner.class)和WebAppConfiguration启动你的测试用例防止意外的错误。
绑定到路由器功能
RouterFunction? route ...
client WebTestClient.bindToRouterFunction(route).build();绑定到服务器
WebTestClient client WebTestClient.bindToServer().baseUrl(http://localhost:8080).build();客户端配置
WebFluxTest
public class MyServiceTest {Testpublic void test() {WebTestClient build WebTestClient.bindToController(new TestController()).configureClient().baseUrl(/test).build();}
}我们使用 configureClient() 方法为 WebTestClient 实例进行配置并通过 baseUrl() 方法设置基本 URL。最后使用 build() 方法创建 WebTestClient 对象并将其分配给 build 变量。
执行请求
WebTestClient提供了一个与WebClient相同的API直到使用exchange()执行请求。在调用exchange()之后WebTestClient从WebClient中分离出来继续使用工作流来验证响应。
WebFluxTest
public class MyServiceTest {Testpublic void test() {WebTestClient build WebTestClient.bindToController(new TestController()).configureClient().baseUrl(/test).build();WebTestClient.ResponseSpec response build.get()//请求方式.uri(/method)//请求地址.exchange();//执行请求}
}除此之外还有其他的方法设置请求参数、请求头示例代码如下
WebFluxTest
public class MyServiceTest {Testpublic void test() {WebTestClient build WebTestClient.bindToController(new TestController()).configureClient().baseUrl(/test).build();WebTestClient.ResponseSpec response build.post()//请求方式.uri(/method)//请求地址.bodyValue(new Object())//设置请求参数.accept(MediaType.APPLICATION_JSON)//设置请求头格式.exchange()//执行请求;}
}响应处理
我们还可以使用断言来进行响应的判断
WebFluxTest
public class MyServiceTest {Testpublic void test() {WebTestClient build WebTestClient.bindToController(new TestController()).configureClient().baseUrl(/test).build();List response build.post().uri(/method).exchange().expectStatus().isOk()//断言方式用于验证期望的 HTTP 响应状态码是否为 200OK。除此之外还有其他比如404、500等.expectHeader().contentType(MediaType.APPLICATION_JSON)//设置响应头格式.expectBody(List.class).consumeWith(result-{//响应体的内容//如果内置断言不够您可以使用该对象并执行任何其他断言:})//.returnResult()//将响应结果转换为 EntityExchangeResult 对象可以通过该对象获取响应状态码、响应头、响应体等信息.getResponseBody()//获取实体对象;}
}expectStatus()还有其他的一些用途如下 .isOk()断言状态码是否为 200。 .isNotFound()断言状态码是否为 404。 .is5xxServerError()断言状态码是否为 5xx服务器错误。 .value(int expectedStatus)断言状态码是否等于 expectedStatus。 expectBody()方法还有其他的一些用途如下 expectBody().isEmpty()验证响应体是否为空。 expectBody().json(String expectedJson)验证响应体的 JSON 内容是否与预期一致。 expectBody().jsonPath(String expression, Object... args)使用 JSONPath 表达式来验证响应体中的字段值。 expectBody().isEqualTo(Object expectedObject)验证响应体是否与预期对象完全相等。 如果您想忽略响应内容可以使用.expectBody(Void.class)上述代码都是经过测试成功跑通的示例不同的注解有点区别
MockMvc
Spring MVC测试框架也称为MockMvc旨在为Spring MVC控制器提供更完整的测试而无需运行服务器。Spring -test模块在没有运行服务器的情况下复制了完整的Spring MVC请求处理。
MockMvc可以单独用于执行请求和验证响应。它也可以通过WebTestClient使用其中插入MockMvc作为处理请求的服务器。WebTestClient的优点是可以使用更高级的对象而不是原始数据还可以切换到针对活动服务器的完整端到端HTTP测试并使用相同的测试API。
要设置MockMvc来测试一个特定的控制器使用以下代码
MockMvc mockMvc MockMvcBuilders.standaloneSetup(new TestController()).build();MockMvcBuilders是Spring MVC Test框架提供的工厂类用于创建MockMvc实例。
你也可以使用ApplicationContext
RunWith(SpringRunner.class)
WebAppConfiguration
public class MyServiceTest {Autowiredprivate WebApplicationContext webApplicationContext;Testpublic void test() {MockMvc mockMvc MockMvcBuilders.webAppContextSetup(webApplicationContext).build();}
}如果你使用ApplicationContext进行启动推荐使用RunWith(SpringRunner.class)和WebAppConfiguration启动你的测试用例防止意外的错误。
执行请求
WebFluxTest
public class MyServiceTest {Testpublic void test() {//standaloneSetup是一个静态方法可以直接调用standaloneSetup(new TestController()).build();MockMvc mockMvc MockMvcBuilders.standaloneSetup(new TestController()).build();try {mockMvc.perform(post(/test/method/{id},1)//请求方式get()、post()方法中第二参数可以传参.accept(MediaType.APPLICATION_JSON)//请求头除了accept()方法还有contentType()方法、header()方法都可以设置请求头.param(name,test)//传参指定字段.content({\name\: \test\, \age\: 18})//传参json格式);} catch (Exception e) {e.printStackTrace();}}
}通过上述示例我们可以知道传参方式有三种可以在请求方法中添加第二个参数、也可以调用.param()方法或者.content()方法。
响应处理
我们可以对响应进行一些处理andExpect()方法来验证模型中是否存在错误属性。
WebFluxTest
public class MyServiceTest {Testpublic void test() {MockMvc mockMvc MockMvcBuilders.standaloneSetup(new TestController()).build();try {MvcResult person mockMvc.perform(get(/test/method)).andExpect(status().isOk())//用于验证期望的 HTTP 响应状态码是否为 200OK。除此之外还有其他比如404、500等.andExpect(content().contentType(MediaType.APPLICATION_JSON))//设置响应头格式.andExpect(model().attributeHasErrors(person))//验证模型中是否存在错误属性.andReturn();//获取执行请求后的返回结果String contentAsString person.getResponse().getContentAsString();} catch (Exception e) {e.printStackTrace();}}
}除了使用 .getContentAsString() 方法获取响应体以字符串形式MvcResult 还提供了其他方法来获取不同类型的响应数据比如 .getResponse().getContentAsByteArray() 可以获取响应体以字节数组形式.getResponse().getHeader() 可以获取响应头信息等。
筛选注册
当设置一个MockMvc实例时你可以注册一个或多个Servlet Filter实例如下例所示
WebFluxTest
public class MyServiceTest {Testpublic void test() {MockMvc mockMvc MockMvcBuilders.standaloneSetup(new TestController()).addFilter(new CharacterEncodingFilter()).build();}
}html单元集成
Spring提供了MockMvc和HtmlUnit。当使用基于HTML的视图时这简化了端到端测试的执行现在大多数项目都是前后分离项目所以并不建议增加后端测试用例的工作量如果有需要可以自行了解。 MockMvc适用于不依赖于Servlet容器的模板技术(例如Thymeleaf、FreeMarker等)但不适用于JSP因为它们依赖于Servlet容器。 JDBC测试支持
org.springframework.test.jdbc包装包含JdbcTestUtils这是一个与JDBC相关的实用函数集合旨在简化标准数据库测试场景。具体来说JdbcTestUtils提供下列静态实用工具方法。
countRowsInTable(..)计算给定表中的行数。countRowsInTableWhere(..)使用提供的计算给定表中的行数WHERE条件。deleteFromTables(..)删除指定表中的所有行。deleteFromTableWhere(..)使用提供的从给定的表中删除行WHERE条件。dropTables(..)删除指定的表。
其它注释
除了前面内容中介绍过的注解再来讲解下没用到的一些注解你不必全都记住只要会一两种能解决你的问题即可。
ContextConfiguration注解
ContextConfiguration定义类级元数据该元数据用于确定如何加载和配置ApplicationContext用于集成测试。具体来说ContextConfiguration声明应用程序上下文资源locations或者组件classes用于加载上下文。
//ContextConfiguration(locations { classpath:/applicationContext.xml })使用 XML 文件配置
//ContextConfiguration(locations { classpath:/com/example/app/ })使用组件扫描
ContextConfiguration(classes { AppConfig.class })//使用 Java 配置类
public class MyServiceTest {
}WebAppConfiguration注解
WebAppConfiguration是一个类级别的注释你可以用它来声明为集成测试加载的ApplicationContext应该是一个WebApplicationContext。
WebAppConfiguration
ContextConfiguration
public class MyServiceTest {
}ContextHierarchy注解
ContextHierarchy是一个类级注释用于定义ApplicationContext集成测试的实例。ContextHierarchy应该用一个或多个ContextConfiguration实例每个实例定义上下文层次结构中的一个级别。
Configuration
ContextHierarchy({ContextConfiguration(classes AppConfig.class),ContextConfiguration(classes DatabaseConfig.class)
})
public class RootConfig {// ...
}DirtiesContext注解
DirtiesContext 注解用于标记测试方法或类会导致应用程序上下文被脏化dirty。当一个测试方法或类被标记为 DirtiesContext 时在执行该方法或类后Spring 将会重置相应的应用程序上下文以确保下一个测试方法或类在一个干净的环境中运行。
RunWith(SpringRunner.class)
public class MyServiceTest {TestDirtiesContextpublic void testMethod() {// ...}Testpublic void anotherTestMethod() {// ...}
}TestExecutionListeners注解
TestExecutionListeners用于自定义测试执行时的监听器。通过使用 TestExecutionListeners 注解我们可以指定在测试方法或类执行期间调用的监听器以扩展测试框架的功能或添加额外的行为。
测试执行监听器可以实现 TestExecutionListener 接口或继承 TestExecutionListenerAdapter 类并覆盖其中的方法来定义自定义的测试执行行为。
public class MyTestListener implements TestExecutionListener {Overridepublic void beforeTestMethod(TestContext testContext) throws Exception {// 在测试方法执行之前执行的逻辑System.out.println(after);}
}
RunWith(SpringRunner.class)
TestExecutionListeners({ MyTestListener.class })
public class MyServiceTest {Testpublic void test() {System.out.println(test);}
}Timed注解
在JUnit 4中通过使用 Timed 注解指示带注释的测试方法必须在指定的时间段(以毫秒为单位)内完成执行。如果文本执行时间超过指定的时间段测试失败。
RunWith(SpringRunner.class)
public class MyServiceTest {TestTimed(millis 10)public void test() {System.out.println(qwe);}
}Repeat注解
在JUnit 4中指示带注释的测试方法必须重复运行。注释中指定了测试方法的运行次数。
RunWith(SpringRunner.class)
public class MyServiceTest {TestRepeat(5)public void test() {System.out.println(qwe);}
}SpringJUnitConfig注解
SpringJUnitConfig是 JUnit 5 中的一个组合注释它结合了ExtendWith(SpringExtension.class)和Spring TestContext框架中的ContextConfiguration我们可以方便地将 Spring 的功能集成到JUnit测试中并使用 Spring 上下文来管理测试环境和依赖注入。 SpringJUnitConfig(AppConfig.class)
public class MyServiceTest {
}SpringJUnitWebConfig注解
SpringJUnitWebConfig是 JUnit 5 中的一个组合注释它结合了JUnit Jupiter中的ExtendWith(SpringExtension.class)和Spring TestContext框架中的ContextConfiguration和WebAppConfiguration。
SpringJUnitWebConfig(TestController.class)
public class MyServiceTest {
}上面介绍的一些*Config注解在实际测试中有很多问题可能是依赖版本导致不支持已经可以跑通的还是JUnit 4版本示例代码
RunWith(SpringRunner.class)
ContextConfiguration(classes AppConfig.class)
public class MyServiceTest {Autowiredprivate ApplicationContext applicationContext;Autowiredprivate MyService myService;Testpublic void test() {System.out.println(myService);System.out.println(applicationContext);}
}如果你想使用WebApplicationContext 做一些处理可以使用WebAppConfiguration注解示例代码如下
RunWith(SpringRunner.class)
WebAppConfiguration
public class MyServiceTest {Autowiredprivate WebApplicationContext applicationContext;Autowiredprivate MyService myService;Testpublic void test() {System.out.println(myService);System.out.println(applicationContext);}
}如果你有更好的使用方法欢迎评论区讨论交流。