基于spring-boot的应用程序的单元+集成测试方案 (3)

@WebMvcTest是特定的注解,它的职责和@SpringBootTest相同,但它只会实例化Controller。默认实例化所有的Controller,也可以指定只实例化某一到多个Controller。

除此之外,@WebMvcTest还会实例化一个MockMvc的bean,用于发送http请求。

我们同样需要对测试目标的依赖进行模拟,即,将CityService声明为MockBean。

spring环境问题

@WebMvcTest就像@SpringBootTest一样,默认搜索@SpringBootConfiguration注解的类作为配置类。一般情况下,基于Spring-Boot的web应用,会创建一个启动类,并使用@SpringBootApplication,这个注解可看作@SpringBootConfiguration注解的扩展,所以很可能会搜索到这个启动类作为配置。

如果项目当中有多个@SpringBootConfiguration配置类,比如有些其他的测试类创建了内部配置类,并且使用了这个注解。如果当前测试类没有使用内部类,也没有使用classes属性指定使用哪个配置类,就会因为找到了多个配置类而失败。这种情况下会有明确的错误提示信息。

思考当前测试类会使用哪一个配置类,是一个很好的习惯。

另外一个可能的问题是:如果配置类上添加了其他的注解,比如Mybatis框架的@MapperScan注解,那么Spring会去尝试实例化Mapper实例,但是因为我们使用的是@WebMvcTest注解,Spring不会去实例化Mapper所依赖的sqlSessionFactory等自动配置的组件,最终导致依赖注解失败,无法构建Spring上下文环境。

也就是说,虽然@WebMvcTest默认只实例化Controller组件,但是它同样也会遵从配置类的注解去做更多的工作。如果这些工作依赖于某些自动化配置bean,那么将会出现依赖缺失。

解决这个问题的方法可能有很多种,我这边提供一个自己的最佳实践:

@RunWith(SpringRunner.class) @WebMvcTest(CityController.class) public class CityControllerWebLayer { @SpringBootApplication(scanBasePackages = {"com.shouzheng.demo.web"}) static class InnerConfig {} @Autowired private MockMvc mvc; @MockBean private CityService service; }

这个方案,是通过使用内部类来自定义配置。内部类只有一个@SpringBootApplication注解,指定了扫描的根路径,以缩小bean的扫描范围。

测试持久层

就像测试controller一样,持久层的单元测试也有专门的注解支持。

持久层的技术有多种,Spring提供了@JdbcTest来支持通过spring的JdbcTemplate进行持久化的测试,提供了@DataJpsTest支持通过JPA技术进行持久化的测试。

上面的这两个注解我没有做过研究,因为项目中使用的是Mybatis,这里仅介绍Mybatis提供的测试支持:@MybatisTest。

最简单的方式是使用内存数据库作为测试数据库,这样可以尽量减少测试的环境依赖。

默认的持久层测试是回滚的,即每一个测试方法执行完成之后,会回滚对数据库的修改;所以也可以使用外部的数据库进行测试,但多少会有些影响(比如序列的当前值)。

使用内存数据库

首先,添加数据库依赖:

<!-- pom.xml --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>RELEASE</version> <scope>test</scope> </dependency>

准备数据库初始化脚本,比如放在resources/import.sql文件中:

drop table if exists city; drop table if exists hotel; create table city (id int primary key AUTO_INCREMENT, name varchar, state varchar, country varchar); create table hotel (city int primary key AUTO_INCREMENT, name varchar, address varchar, zip varchar); insert into city (id, name, state, country) values (1, 'San Francisco', 'CA', 'US'); insert into hotel(city, name, address, zip) values (1, 'Conrad Treasury Place', 'William & George Streets', '4001')

需要在配置文件中指定脚本文件的位置:

spring.datasource.schema=classpath:import.sql

例如我们要测试如下的Mapper接口:

@Mapper public interface CityMapper { City selectCityById(int id); List<City> selectAllCities(); int insert(City city); }

我们可以这样编写测试类:

@RunWith(SpringRunner.class) @MybatisTest @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class CityMapperUnitTest { @SpringBootApplication(scanBasePackages = {"com.shouzheng.demo.mapper"}) static class InnerConfig {} private static Logger LOG = LoggerFactory.getLogger(CityMapperUnitTest.class); @Autowired private CityMapper cityMapper; @Before @After public void printAllCities() { List<City> cities = cityMapper.selectAllCities(); LOG.info("{}", cities); } @Test // @Rollback(false) // 禁止回滚 public void test1_insert() throws Exception { City city = new City(); city.setName("杭州"); city.setState("浙江"); city.setCountry("CN"); cityMapper.insert(city); LOG.info("insert a city {}", city); } @Test public void test2_doNothing() { } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdfwd.html