1、Spring测试类中的问题和解决思路

1.1 问题

在单元测试类中,每个测试方法都有以下两行代码:

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService as = ac.getBean("accountService",AccountService.class);

这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。

1.2 JUnit测试是什么?

  • JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个。 JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
  • JUnit是由Erich Gamma 和 Kent Beck 编写的一个回归测试框架。JUnit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何完成功能和完成什么样的功能。JUnit是一套框架,继承TestCase类,就可以用JUnit进行自动测试了。
  • JUnit测试类似于c语言中的调试,只不过那个调试是通过设置断点分隔代码来进行测试;而java中的JUnit则是通过分开测试不同的类和方法实现的。

1.3 解决思路分析

  • 针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建spring容器,我们就无须手动创建了,问题也就解决了。
  • 在此之前我们应该都知道,JUnit 单元测试的原理,但显然,JUnit 是无法实现的,因为它自己都无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在,JUnit 给我们暴露了一个注解,可以让我们替换掉它的运行器。
  • 这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我们只需要告诉它配置文件在哪就行了。

2、配置步骤

一、首先整合JUnit4

2.1 引入整合JUnit以及Spring针对测试相关的Maven依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.6.RELEASE</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

2.2 创建测试类,使用注解完成

/**
 * Spring整合Junit4的测试类
 *
 * @Author cappuccino
 * @Date 2021-05-18 22:24
 */
@RunWith(SpringJUnit4ClassRunner.class) //使用@RunWith注解替换原有的运行器
@ContextConfiguration(locations = "classpath:applicationContext.xml") //使用@ContextConfiguration 指定 spring 配置文件的位置
public class SpringJUnit4Test {

    @Autowired
    private UserService userService;

    @Test
    public void testUserQueryById2(){
        User user = userService.queryById(1);
        System.out.println(user);
    }

//    @Test
//    public void testUserQueryById1(){
//        ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
//        UserService userService = app.getBean("userService", UserService.class);
//        User user = userService.queryById(1);
//        System.out.println(user);
//    }
}

@ContextConfiguration 注解:
locations属性:用于指定配置文件的位置。如果是类路径下,需要用 classpath: 表明
classes属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。

二、Spring5整合JUnit5

1.JUnit5和之前的JUnit版本不一样,JUnit5是由三个模块组成。

JUnit5 = JUnit Platform + JUnit Jupiter + JUnit Vintage。

  • JUnit Platform是在jvm上启动测试框架的基础,定义了测试引擎的API,可以在cmd命令行启动这个平台。
  • JUnit Jupiter是新编程模块和扩展模块在junit5上写测试和扩展的组合,Jupiter子工程提供在平台上跑Jupiter的测试引擎。
  • JUnit Vintage提供跑junit3和junit4的测试引擎。

2.支持的java版本:

junit5需要java8及以上的版本。但是可以测试用以前的jdk版本编译过的代码。

3.引入JUnit5相关的Maven依赖

<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-launcher</artifactId>
    <version>1.0.0-RC2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.0.0-RC2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <version>4.12.0-RC2</version>
    <scope>test</scope>
</dependency>

4. 创建测试类,使用注解完成

/**
 * Spring整合Junit5的测试类
 * @Author cappuccino
 * @Date 2021-05-18 22:27
 */
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringJUnit5Test {
    @Autowired
    private UserService userService;

    @Test
    public void testUserQueryById(){
        User user = userService.queryById(1);
        System.out.println(user);
    }
}

5.使用一个复合注解替代上面两个注解完成整合

/**
 * Spring整合Junit5的测试类
 * @Author cappuccino
 * @Date 2021-05-18 22:27
 */
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")//指定配置文件路径,会先从test域中找
public class SpringJUnit5Test {
    @Autowired
    private UserService userService;

    @Test
    public void testUserQueryById(){
        User user = userService.queryById(1);
        System.out.println(user);
    }
}

三、常见JUnit4和JUnit5的区别以及常用注解

JUnit4JUnit5说明
@Test@Test表示该方法是一个测试方法。JUnit5与JUnit 4的@Test注解不同的是,它没有声明任何属性,因为JUnit Jupiter中的测试扩展是基于它们自己的专用注解来完成的。这样的方法会被继承,除非它们被覆盖
@BeforeClass@BeforeAll表示使用了该注解的方法应该在当前类中所有使用了@Test @RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法之前 执行;
@AfterClass@AfterAll表示使用了该注解的方法应该在当前类中所有使用了@Test、@RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法之后执行;
@Before@BeforeEach表示使用了该注解的方法应该在当前类中每一个使用了@Test、@RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法之前 执行
@After@AfterEach表示使用了该注解的方法应该在当前类中每一个使用了@Test、@RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法之后 执行
@Ignore@Disabled用于禁用一个测试类或测试方法
@Category@Tag用于声明过滤测试的tags,该注解可以用在方法或类上;类似于TesgNG的测试组或JUnit 4的分类。
@Parameters@ParameterizedTest表示该方法是一个参数化测试
@RunWith@ExtendWith@Runwith就是放在测试类名之前,用来确定这个类怎么运行的
@Rule@ExtendWithRule是一组实现了TestRule接口的共享类,提供了验证、监视TestCase和外部资源管理等能力
@ClassRule@ExtendWith@ClassRule用于测试类中的静态变量,必须是TestRule接口的实例,且访问修饰符必须为public。

为什么不把测试类配到 xml 中

  • 在解释这个问题之前,先解除大家的疑虑,配到 XML 中能不能用呢?
  • 答案是:肯定的,没问题,可以使用。
  • 那么为什么不采用配置到 xml 中的方式呢?
  • 这个原因是这样的:
    第一:当我们在 xml 中配置了一个 bean,spring 加载配置文件创建容器时,就会创建对象。
    第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。
    所以,基于以上两点,我们不应该把测试配置到 xml 文件中。

Q.E.D.


奔浪少年永不停歇,勇敢追逐星河灿烂。