写点什么

一篇文章让你了解基于 Spring 的测试,java 自学百度网盘

用户头像
极客good
关注
发布于: 刚刚

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 为例,在 Spr


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


ing 测试框架下编写测试类的步骤如下:


  1. 在测试类上使用 @RunWith 注解测试运行器。

  2. 使用 @ContextConfiguration 注解指定 Spring 的配置(可以是 XML 配置文件,也可以是配置类)。

  3. 装载需要的 Bean 并完成 JUnit 标签 @Test 注解的测试方法。


完整的代码实例如下:

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
一篇文章让你了解基于Spring的测试,java自学百度网盘