前言
在前文中,我们也使用了测试代码来进行简单的单元测试,但是我们会发现,里面有大量的重复代码,实际给我们的体验并不是太好,所以这篇,我们来学习Spring Test,Spring Test不仅仅限于在Mybatis框架,只要是基于Spring的框架的都可以使用Spring Test,使用Spring Test,将给测试模块带来质的改善,大大提高了自测的效率。接下来,我们就来学习Spring Test的用法和注意事项吧。
Spring Test的作用
在普通测试环境下,我们在使用Spring的时候,需要手动加载Spring配置,手动从Spring容器中获取对象,前文中的使用全是如此,这也就违背了我们使用Spring框架的意愿:自动创建对象,自动管理对象。我们把这种用法叫自动装配。
Spring还有一个用处,使用@Sql注解,此注解可在测试类方法之前定义,提前或延后执行某段sql语句,在测试中也经常使用。
在项目中加入Spring Test
添加依赖
<!--Spring Test依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.14</version>
</dependency>
Spring Test无法单独工作,仍需配合其他测试依赖项和其他Spring依赖一起使用,可参照Mybatis一文中的依赖进行添加,亦可在原项目中直接操作。但要注意,要和其他Spring依赖项的版本保持一致,切记。
创建测试类
package cn.codingfire.mybatis;
public class MybatisTest {
}
此时需在测试类上添加@SpringJUnitConfig注解:
package cn.codingfire.mybatis;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(SpringConfig.class)
public class MybatisTest {
}
接着我们可以在此类中添加Spring的配置类,这样,在此类中任何方法之前,都会先加载Spring的配置类,Spring容器中存在的类就都可以实现自动装配了。我们以环境变量为例:
package cn.codingfire.mybatis;
import cn.codingfire.mybatis.config.SpringConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(SpringConfig.class)
public class MybatisTest {
@Autowired
Environment env;
@Test
public void testEnvironment() {
System.out.println(env.getProperty("datasource.url"));
System.out.println(env.getProperty("datasource.driver"));
System.out.println(env.getProperty("datasource.username"));
System.out.println(env.getProperty("datasource.password"));
}
}
接着运行此测试方法,查看输出:
已经成功输出了我们在properties文件中配置的信息。对比之前Mybatis中的测试方法如下:
@Test
public void loadBasicInfo() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
ConfigurableEnvironment environment = ac.getEnvironment();
System.out.println(environment.getProperty("datasource.url"));
System.out.println(environment.getProperty("datasource.driver"));
System.out.println(environment.getProperty("datasource.username"));
System.out.println(environment.getProperty("datasource.password"));
ac.close();
}
先获取ac,再获取environment,最后再关闭ac,简化了太多步骤。使用Spring Test,我们只需关注测试的内容本身,而不用去管环境的问题,效果要更好。再增删改查时也不需要再关注开头和结尾的那几段代码,这种重复性的操作被省略,也是自动装配的精髓之一。
关于@Autowired注解,就是自动装配的意思,我们可以尝试着给其他的对象添加此注解:
会看到AdminMapper报一个错,这里有个小知识点。这是因为编译器问题导致无法识别,解决办法是在AdminMapper的类中添加@Repository注解,回来后再看,正常来说报错会消失,但有的人的不会消失,可在注解里添加required属性为false:
此时,问题已经解决了,它的意思是,能装配上就装,不能装配也不强求。有意思的是,即使你不管这个报错,方法也可正常运行,不存在任何影响。
Spring Test下的测试方法
我们以插入方法为例,做个前后对比。
未使用Spring Test的插入方法测试:
@Test
public void testInsert() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
AdminMapper adminMapper = ac.getBean(AdminMapper.class);
Admin admin = new Admin();
admin.setUsername("admin04");
admin.setPassword("123456");
adminMapper.insert(admin);
ac.close();
}
使用了Spring Test的插入方法测试 :
@Test
public void testInsert() {
Admin admin = new Admin();
admin.setUsername("admin04");
admin.setPassword("123456");
adminMapper.insert(admin);
}
前后对比明显,自动装配后,adminMapper由系统创建管理,可在类中直接使用,简化了代码。
@Sql注解
@Sql注解的作用和注意事项
Spring Test测试类中,还可以使用@Sql注解,他可以加载某些脚本.sql的脚本,可以在测试前后执行一些给定的sql语句。它的作用是可以在测试时进行反复测试,在Mybatis中,我们在测试时,有时为了使mapper的方法运行成功,需要运行插入的方法,这就很不友好了,增加了测试的成本,比如我删除某条数据后,表中没有数据,我要再执行删除操作前,必须要再插入一条数据,否则会报错,而我使用@Sql注解,就可以解决这个问题,使得每次测试不需要再关注其他的方法。
使用此注解要注意几个问题:
- @Sql注解可以添加在单独的方法中,仅对此方法有效,也可添加在测试类上,对类中所有的方法有效。如果类和方法上都添加了相同的@Sql注解,仅方法上的生效
- 可方法前执行.sql脚本,也可方法后执行.sql脚本,通过executionPhase属性来管理
- @Sql注解可添加多个
@Sql注解怎么用
首先,我们需要先创建一个.sql文件,选择file,创建一个truncate.sql:
在test下的resoutces文件中创建,在此文件中可以看到和再sql工具中一样,是有sql提醒的。 truncate的意思是截断,在sql中意味着清空整张表。我们知道,数据库表中不允许插入相同的两条数据,否则就会报重复的错误,使用此注解,在每次插入前都清空整张表就可以频繁测试,看代码:
@Sql(scripts = {"classpath:truncate.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Test
public void testInsert() {
Admin admin = new Admin();
admin.setUsername("admin04");
admin.setPassword("123456");
adminMapper.insert(admin);
}
可以在插入前清空整张表,以达到频繁插入的测试。
以删除为例,我们想删除的时候数据库表中一直有数据。此时,也可以使用此注解,接下来我们来说说怎么同时使用多个.sql脚本,首先创建一个插入的.sql脚本:
看代码:
@Sql(scripts = {"classpath:truncate.sql", "classpath:insert.sql"})
@Test
public void testDelete() {
adminMapper.deleteById(1L);
}
每次执行次方法都是成功的。如果你想在方法执行后再执行某些sql的话,可以设置@Sql的executionPhase属性为Sql.ExecutionPhase.AFTER_TEST_METHOD:
executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD
断言
我们通常在测试类中使用Assertions类的静态方法对测试结果进行预测,帮助我们发现代码中可能存在的问题,一旦不符合预测的正确结果就会报错,大大提高代码的正确性。常用的一些断言方法有:
- assertEquals():断言匹配(相等)
- assertNotEquals():断言不匹配(不相等)
- assertTrue():断言为“真”
- assertFalse():断言为“假”
- assertNull():断言为null
- assertNotNull():断言不为null
- assertThrows():断言抛出异常
- assertDoesNotThrow():断言不会抛出异常
- 其他
接下来我们挑几个在代码中来看使用效果。
assertEquals():
@Sql(scripts = {"classpath:truncate.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
@Test
public void testInsert() {
Admin admin = new Admin();
admin.setUsername("admin04");
admin.setPassword("123456");
int index = adminMapper.insert(admin);
System.out.println(index);
Assertions.assertEquals(1,index);
}
Assertions.assertEquals 有两个参数,第一个是expected,是期望的值,第二个是ectual,是实际的值,如果预测的不对,就会报错,正确则没有任何反应。
assertTrue()
还以插入为例,看代码:
@Sql(scripts = {"classpath:truncate.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
@Test
public void testInsert() {
Admin admin = new Admin();
admin.setUsername("admin04");
admin.setPassword("123456");
int index = adminMapper.insert(admin);
System.out.println(index);
boolean isThanZero = index > 0 ? true : false;
Assertions.assertTrue(isThanZero);
}
断言isThanZero,即影响的行数大于0,则说明插入成功,否则将报异常。
assertNull()
以根据id获取数据为例:
@Test
public void getById() {
Admin admin = adminMapper.getById(10L);
Assertions.assertNull(admin);
}
id为10的数据表中没有,所以语言admin为null,是正确的,符合我们的预期,不会报错。
assertThrows()
前面我们说过重复插入数据会把哦重复插入的异常,这时就不能在插入前清空表了,我们以此为例,看代码:
@Test
public void testInsert() {
Admin admin = new Admin();
admin.setUsername("admin04");
admin.setPassword("123456");
Assertions.assertThrows(DuplicateKeyException.class, () -> {
adminMapper.insert(admin);
});
}
正常来说,连续执行两次,就会抛出重复插入的异常,但是我们做了断言后,就不会有任何输出,反而会在第一次执行时抛出下面这段异常:
org.opentest4j.AssertionFailedError: Expected org.springframework.dao.DuplicateKeyException to be thrown, but nothing was thrown.
意思是说,我们预测会抛出重复的异常,但是什么也没有抛出。这是正常的,因为第一次插入成功了。
到这里,断言就写完了,上面列出来的每一类中的一个都给出了案例 ,照葫芦画瓢,对另一个取反就是另一个,相信聪明如大家已经知道该怎么用了,不再赘述。
结语
最近几天,这篇算是最短的了,写起来也最省劲,用了不到半天就写完了,虽然简单,但是里面的知识却很重要,最好结合前面的SSM框架一起来看和使用,可以达到事半功倍的效果。代码要练习,光看是不行的,不上手,就看不到输出,就容易忽略一些细节,希望大家都能学的贼溜,明年拿高薪。