如果要结合容器进行集成测试,在测试类中的固件初始化时(比如@Before注解方法)初始化容器后获取需要的Bean实例进行测试,以JUnit4中初始化容器进行测试的代码示例如下:
public class JunitSpringTest { //单元测试类
private ApplicationContext context; //定义上下文变量
@Before //测试环境初始化注解
public void initSpring() { //环境初始化方法
context = new ClassPathXmlApplicationContext("applicationContext.xml");//初始上下文
}
@Test //注解测试方法
public void testMethod1() { //测试方法
HelloBean bean1 = (HelloBean)context.getBean("bean1"); //从容器获取bean实例
}
}
以上在initSpring()方法中,通过ClassPathXmlApplicationContext初始化容器后就可以从容器中获取Bean进行测试。上面的代码段是较为常见的Spring测试示例,虽然可以达成测试效果,但是存在着如下问题:
频繁初始化容器,开销大、效率低且浪费资源
====================
在JUnit4中,@Before注解在每个标准@Test方法之前都会执行,这就意味着这个测试类中有多少个@Test标识的方法,Spring容器就会被重复化初始多少次。如果容器在初始化时要加载ORM映射和数据源,这笔开销还是很可观的。这个问题虽然可以通过使用@BeforeClass注解修改成类层级的初始化,但是在整个项目使用Maven进行批量测试时,对应每个测试类,都会初始化一个新的容器。
这种效果可以在Eclipse使用Maven批量测试进行验证。新建两个测试类,打印ApplicationContext对象的id,可以看到每个测试类的应用上下文都不一样的,验证的代码如下:
public class JUnitSpring1Test { //测试类
private static ApplicationContext context; //静态应用上下文
@BeforeClass //类层级环境初始化注解
public static void initSpring() { //类层级环境初始化
context = new ClassPathXmlApplicationContext("applicationContext.xml");//上下文
}
@Test //注解测试方法
public void testMethod() { //测试方法
System.out.println("JUnitSpring1Test, applicationContext=" + context.toString());
}
}
测试代码繁琐、冗余
=========
对每个Bean的测试都要先通过getBean()方法从容器中先获取对应的实例,之后做强制类型转换之后方可使用和测试。在涉及多个Bean的集成测试的时候,这样重复额外的代码会极大的影响测试效率。
在数据持久化处理上不便捷
============
在开发时候的单元测试,或是使用工具批量的单元测试,很多状况希望测试方法不要将数据操作持久化到数据库中。虽然可以在方法层级上添加事务的处理,但是针对测试的特别改动有可能出现忘记回退而影响实际的功能。
针对上面那些问题,Spring提供了专门的测试模块来简化测试工作和加快测试的效率。
Spring测试模块
==========
Spring提供了通用的、注解驱动的测试模块(spring-test)辅助进行Spring相关的测试。spring-test支持结合JUnit或TestNG框架来进行单元测试和集成测试。spring-test的测试模块需要导入,Maven的导入方式直接在pom.xml中加入依赖项,类似:
<dependency>
<groupId>org.springframework</groupId> <!—组名
<artifactId>spring-test</artifactId> <!—组件名
<version>5.0.8.RELEASE</version> <!—版本
</dependency>
此外,Spring是结合JUnit或TestNG进行测试,所以需要确保对应的测试框架导入。
spring-test测试模块的代码目录结构如图1所示。
图1 Spring测试模块的目录结构
测试模块分为mock和test两大部分,mock包里面提供了四种类型的模拟对象,可以单独使用于单元测试。context和web分别对应核心框架和Web框架的测试支持;annotation是用于测试的注解定义;jdbc包提供了数据库测试的支持;util包提供了一些公用方法可以用于脱离Spring测试框架进行独立的单元测试。
Spring测试模块可以结合单元测试框架,对一般桌面应用和Web应用进行测试。针对两种不同类型应用的测试,Spring对应的有通用测试框架和MVC Web的测试框架。使用Spring测试框架,应用上下文可以一次性加载并进行缓存,大大提高了测试的速度。更方便的是其提供了很多便捷的注解辅助测试。
Spring测试模块对单元测试的支持
==================
Spring对单元测试的支持主要有两个方面:提供了多种类型的模拟对象和提供了用于单元测试的一些共用方法。
Spring测试模块的模拟类
==============
对于简单的POJO和服务端的类和方法,可以使用Mock框架模拟创建,但对于复杂的状况,Mock框架不易处理。Spring提供了更高层级的类的模拟,包含:
1、环境的模拟
Java中的Property是以键值对方式存储的数据类型,PropertyResolver是Spring提供的属性解析器,可以通过key查找对应的值,Environment继承自PropertyResolver,但是额外提供了Profile特性,即可以根据不同环境(比如:开发、测试和正式环境)得到相应数据。MockEnvironment就是对Environment的模拟,可以在测试时设置需要的属性键和值。使用的示例如下:
//代码方式的bean配置和注册
DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); //bean工厂
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();//Bean定义
beanDefinition.setBeanClass(User.class); //设置bean定义类
beanDefinition.getPropertyValues().add("name", "${name}") //设置bean属性;使用占位符
bf.registerBeanDefinition("user", beanDefinition); //注册bean
//模拟环境并设置属性后,对bean中的占位符进行替换
PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
pc.setEnvironment(new MockEnvironment().withProperty("name", "Oscar"));//设置环境属性
pc.postProcessBeanFactory(bf); //替换占位符
上面的示例代码中,使用GenericBeanDefinition类进行Bean的配置和注册,对应的Bean类是User,使用占位符${name}设置该类的属性值。接着创建MockEnvironment和设置了属性name的值,最后设置PropertySourcesPlaceholderConfigurer的环境为创建的模拟环境并替换占位符为模拟环境中设置的属性值。
2、JNDI的模拟
JNDI(Java Naming and Directory Interface),Java命名与目录接口。直观点理解就是给资源(比如数据库资源)一个通用的名字,通过这个名字就可以查找这个资源了。
在开发和测试时,数据源可以通过配置url、name和password在XML中配置,但在正式环境中,为了保障安全,数据源更多的是使用JNDI的方式配置在应用服务器上,而且除了数据源以外的其他资源,类似EJB等就必须使用JNDI的方式了。Spring提供了根据JNDI名称查找资源对象的类JndiObjectFactoryBean,以JNDI:“java:comp/env/jdbc/mydatasource”为例,通过XML配置数据源bean的方式如下:
<bean id="jndiDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"> <!—JNDI属性定义
<value>java:comp/env/jdbc/mydatasource</value>
</property>
</bean>
现在问题是:测试的时候不希望开启应用服务器,怎么找到JNDI对应的资源呢?Spring提供了使用SimpleNamingContextBuilder来构造JNDI资源的模拟。以上面配置的数据源的JNDI模拟为例:
public void initForTest() throws IllegalStateException, NamingException { //模拟JNDI方法
DriverManagerDataSource ds = new DriverManagerDataSource(); //创建数据源
ds.setDriverClassName("com.mysql.cj.jdbc.Driver"); //驱动类设置
ds.setUrl("jdbc:mysql://localhost:3306/ssmi?serverTimezone=UTC");//数据源url
ds.setUsername("root"); //用户名
ds.setPassword("123456"); //密码
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder.bind("java:comp/env/jdbc/mydatasource", ds); //绑定数据源
builder.activate();//激活
}
以上通过DriverManagerDataSource创建一个数据源,使用SimpleNamingContextBuilder给该资源绑定一个JNDI的名字。以上方法可以使用@Before注解后,读取包含以上JNDI的配置文件就可以使用模拟的数据源了。DB及数据源相关的部分会在后面章节深入介绍。
3、HTTP和Web相关的模拟
Spring提供了Http和Servlet的模拟对象类用于Web测试,相关部分的会在后面Spring Web测试中介绍,另外,还提供了Spring Web Reactive的响应式 Web测试的模拟对象,本书不做探讨。
Spring测试模块的共用方法
===============
org.springframework.test.util包中的ReflectionTestUtils类,提供了基于反射机制的方法,可以修改变量、非公有的属性和访问非公有的方法,还可以调用生命周期回调方法。在基于Spring框架开发中,经常使用注解(包括@Autowired、@Inject和@Resource等)对私有的方法或属性进行依赖注入,这些私有的变量和方法不能直接获取和调用。举例来看,组件类Foo定义如下:
public class Foo { //组件类
@Autowired //自动装载注解
private String name; //字符串变量
@PostConstruct //组件初始化注解
private void onInit(){ //组件初始化回调方法
System.out.println("onInit... " + name);
}
@PreDestroy //组件销毁注解
private void onDestroy(){ //组件销毁方法回调
System.out.println("onDestroy... " + name);
}
}
该组件类使用@Autowired注解了一个私有的属性name,@PostConstruct、@PreDestroy分别注解了两个私有的回调方法。这种代码风格是实际开发中常见的风格,可是在单元测试中不启动Spring容器,无法得到依赖对象也无法执行注解的生命周期回调方法下如何测试呢?答案就是通过ReflectionTestUtils,测试代码如下:
@Test //测试方法注解
public void test () { //测试方法
Foo foo = new Foo(); //组件创建
ReflectionTestUtils.setField(foo, "name", "Oscar"); //设置私有变量的值
ReflectionTestUtils.invokeMethod(foo, "onInit"); //调用组件初始化方法
ReflectionTestUtils.invokeMethod(foo, "onDestroy"); //调用组件销毁方法
}
Spring测试框架
==========
Spring测试框架在JUnit和TestNG框架之上进行扩展。在测试类似使用JUnit的@RunWith(JUnit4)或@ExtendWith(JUnit5)指定Spring对应的运行器扩展,就可以在测试类中很容易进行上下文的初始化和缓存。通过测试执行监听器,可以在测试类中使用依赖注入和事务管理等注解,也可以自定义注解,简化测试。
Spring测试框架使用
============
使用Spring框架开发的应用,更常使用的是对容器初始化之后的测试,也就是严格意义上的集成测试。Spring测试框架主要位于org.springframework.test.context包中,提供了通用的、注解驱动和集成测试支持。其无缝集成了JUnit和TestNG的测试框架。
以JUnit4为例,在Spring测试框架下编写测试类的步骤如下:
- 在测试类上使用@RunWith注解测试运行器。
- 使用@ContextConfiguration注解指定Spring的配置(可以是XML配置文件,也可以是配置类)。
- 装载需要的Bean并完成JUnit标签@Test注解的测试方法。
完整的代码实例如下:
# Spring全套教学资料
**Spring是Java程序员的《葵花宝典》,其中提供的各种大招,能简化我们的开发,大大提升开发效率!目前99%的公司使用了Spring,大家可以去各大招聘网站看一下,Spring算是必备技能,所以一定要掌握。**
**[CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】]()**
**目录:**
![]()
![]()
**部分内容:**
![]()
![]()
# Spring源码
* 第一部分 Spring 概述
* 第二部分 核心思想
* 第三部分 手写实现 IoC 和 AOP(自定义Spring框架)
* 第四部分 Spring IOC 高级应用
基础特性
高级特性
* 第五部分 Spring IOC源码深度剖析
设计优雅
设计模式
注意:原则、方法和技巧
* 第六部分 Spring AOP 应用
声明事务控制
* 第七部分 Spring AOP源码深度剖析
必要的笔记、必要的图、通俗易懂的语言化解知识难点
![]()
![]()
# 脚手框架:SpringBoot技术
> 它的目标是简化Spring应用和服务的创建、开发与部署,简化了配置文件,使用嵌入式web服务器,含有诸多开箱即用的微服务功能,可以和spring cloud联合部署。
>
> Spring Boot的核心思想是约定大于配置,应用只需要很少的配置即可,简化了应用开发模式。
* SpringBoot入门
* 配置文件
* 日志
* Web开发
* Docker
* SpringBoot与数据访问
* 启动配置原理
* 自定义starter
![]()
![]()
# 微服务架构:Spring Cloud Alibaba
> 同 Spring Cloud 一样,Spring Cloud Alibaba 也是一套微服务解决方案,包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
* 微服务架构介绍
* Spring Cloud Alibaba介绍
* 微服务环境搭建
* 服务治理
* 服务容错
* 服务网关
* 链路追踪
* ZipKin集成及数据持久化
* 消息驱动
* 短信服务
* Nacos Confifig—服务配置
* Seata—分布式事务
* Dubbo—rpc通信
![]()
![]()
# Spring MVC
**目录:**
![]()
![]()
![]()
**部分内容:**
![]()
![]()
微服务环境搭建
* 服务治理
* 服务容错
* 服务网关
* 链路追踪
* ZipKin集成及数据持久化
* 消息驱动
* 短信服务
* Nacos Confifig—服务配置
* Seata—分布式事务
* Dubbo—rpc通信
[外链图片转存中...(img-KEArw4HZ-1631089915614)]
[外链图片转存中...(img-vSk7RqI9-1631089915614)]
# Spring MVC
**目录:**
[外链图片转存中...(img-YTO5MUAT-1631089915615)]
[外链图片转存中...(img-6zpjSubW-1631089915615)]
[外链图片转存中...(img-qRgj6ht1-1631089915616)]
**部分内容:**
[外链图片转存中...(img-7L9RdkQX-1631089915617)]
[外链图片转存中...(img-Iz6LtEs9-1631089915617)]