[关闭]
@survivorZzz 2018-11-21T10:06:13.000000Z 字数 4976 阅读 1383

基于Spring boot项目的开发测试

单元测试 Spring-boot-test


术语

单元测试
用于测试最小功能单元,比如单个方法内的业务逻辑的测试。单元测试就是最小代码单元的针对性测试,可以是对象的一个属性,检查是否存在或值是否有效,也可以是一个函数或方法,检查其行为或输出是否如预期或者代码执行效能等, 通常不关心单元呢所依赖模块的方法调用(可以通过mock的方式)。
简而言之,单元测试应该尽可能的简单、易于调试、可靠、快速执行,用来帮助你检查你的程序的最小模块是否工作得符合你的预期。值得注意的是,虽然单元测试可以证明模块独立工作时是正常的,但是无法证明模块组合在一起后也能正常工作。
在项目中, 我们在service层(所依赖的Mapper接口通过mock的方式来模拟)或持久层做的测试通常是单元测试.

集成测试
集成测试是在单元测试的基础上,将所有模块按照设计要求组装成为子系统或系统,验证组装后功能以及模块间接口是否正确的测试。集成测试也叫组装测试、联合测试、子系统测试或部件测试。
在项目中, 我们通常在Controller层做非严格意义的集成测试,这里说非严格, 是因为我们队feign接口的调用也是通过mock的方式来模拟,避免了需要启动其他业务服务的项目.

mock
Mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来模拟那些对象以便测试的方法。
引入Mock最大的优势在于:Mock的行为固定,它确保当你访问该Mock的某个方法时总是能够获得一个没有任何逻辑的直接就返回的预期结果。
在petrel后端项目中, 举例...
可用的mock框架有很多, 例如EasyMock,PowerMock,JMock等, 推荐使用Mockito. spring-boot-starter-test中已经集成了Mockito-core.
13.png-21.2kB


petrel后端怎么做测试

由于petrel的后端工程都是基于springboot的项目所以,首先需要在maven pom中添加测试需要的起步依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-test</artifactId>
  4. <verison>${spring.boot.version}</version>
  5. <scope>test</scope>
  6. </dependency>

spring-boot-starter-test 起步依赖中集成了Junit, spring-test, mockito, hamcrest, json-path等用于测试和测试结果断言验证的框架组件.

在我们后端项目中,一般分为Controller层、service层、持久层。上述各组件的职责如下:

在基于Spring项目中,我们通常会把这三层的实现类注册到Spring容器,让spring帮我们管理这些组件的生命周期和依赖管理. 那怎么才能在JUnit中使用Spring容器中的组件呢?
首先,在Spring boot项目的测试类上除了使用JUnit提供的注解@RunWith(SpringRunner.class)注解里面的value值SpringRunner.class可以让Junit获得spring context支持, 包括依赖注入等功能.
其次,使用spring boot test提供的@SpringBootTest注解可以让我们为Junit测试类指定一个spring context (通过classes属性值指定spring boot入口类, 如果不指定, 会自动在test包路径下找被@SpringBootApplication标注的类, 以加载一个spring容器), 以下是@SpringBootTest的javadoc, 也许对大家对该注解的重要作用的理解有所帮助:

  • Uses SpringBootContextLoader as the default ContextLoader when no specific @ContextConfiguration(loader=...) is defined.
  • Automatically searches for a @SpringBootConfiguration when nested @Configuration is not used, and no explicit classes are specified.
  • Allows custom Environment properties to be defined using the properties attribute.
  • Provides support for different webEnvironment modes, including the ability to start a fully running container listening on a defined or random port.
  • Registers a TestRestTemplate bean for use in web tests that are using a fully running container.

以上两个标注在测试类上的注解便是spring boot单元测试所需要的最重要的两个注解.

这里是一个测试类例子:

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. @Transactional
  4. @Rollback
  5. public class BaseServiceTest {
  6. @Before
  7. public void setUp() {...}
  8. @test
  9. public void test1() {test something ....}
  10. ...
  11. }

@Transactional是事务注解, 在测试类上加上该注解则该测试类下的所有测试方法均被事务管理.
@Rollback是一个spring test的注解, 添加在测试类上测该测试类内的所有测试方法向数据库做的非读操作的事务都会在每个测试方法结束之后回滚. 当然, 也可以在具体的测试方法上标注@Rollback(false)让该测试方法持久数据变更到数据库, 但一般不推荐这么做,这样会因为单元测试产生很多脏数据和垃圾数据.

实际开发中我们的petrel后端项目应该怎么做测试呢?
我们暂时约定就只对service层和Controller层做测试.
包结构规范
举例...
命名规范
举例...
Service层:
演示举例...
Controller层
演示举例...
Mockito用法举例
演示举例...
mockmvc是啥, 使用mockmvc和不使用的差别
Spring mockMvc我的理解就是启动一个虚拟的servlet 容器(类比tomcat), 然后初始化Spring mvc的基础组件.就像真实的运行起来使用了Spring mvc的Spring boot项目一样. 所以我们在使用Spring mockMvc测试Controller层的时候, 就不需要把我们的项目真实地起来, 也一样可以像发送真实请求那样去测试我们的Controler提供的REST Api.

当然, 如果不想使用Spring mocMvc去测试Controller, 那我们可以用这样写:

  1. /**
  2. * 一个非mockmvc测试类
  3. */
  4. @RunWith(SpringRunner.class)
  5. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
  6. public class NoMockMvcTest {
  7. private Logger logger = LoggerFactory.getLogger(NoMockMvcTest.class);
  8. @Autowired
  9. private TestRestTemplate restTemplate;
  10. @Test
  11. public void testFindNoMockMvc() {
  12. String responseBodyJson = restTemplate.getForObject("http://localhost:10005/itg/api/sysCompany/find.json", String.class);
  13. assertThat("测试失败!", CommonUtil.isNotNull(responseBodyJson));
  14. //更多详细的结果断言省略...
  15. logger.debug(responseBodyJson);
  16. }
  17. }

测试这个方法的时候需要把接口所在的服务器真实地起来才请求得到.这就是和使用MockMvc的区别.

我实际使用的是Rest Assured的RestAssuredMockMvc, 来mock mvc. 和Spring MockMvc相比比较好的是rest assured 读起来更加的符合语言习惯比如:

  1. RestAssuredMockMvc
  2. .given().
  3. parameters("firstName", "John", "lastName", "Doe").
  4. when().
  5. post("/greetXML").
  6. then().
  7. body("greeting.firstName", equalTo("John")).
  8. body("greeting.lastName", equalTo("Doe"));

感受一下!

rest-assured
这个就不展开将了, 它是一个很方便, 学习成本也不大, 可读性也比较强的测试框架, 具体的使用方法参见最下面给的参考文档.
演示举例...

制造测试数据
我们做测试关注的另一个点,是测试时,如何制造测试数据,以及消除这些测试数据, 以保证幂等。目前大家用的方案大致有如下两种:

  1. <dependency>
  2. <groupId>com.h2database</groupId>
  3. <artifactId>h2</artifactId>
  4. <version>${h2.version}</version>
  5. <scope>test</scope>
  6. </dependency>

通常配置内存数据库的配置如下:

  1. #指定初始化脚本位置
  2. spring.datasource.schema=classpath:db/schema.sql
  3. spring.datasource.data=classpath:db/data.sql
  4. #配置h2
  5. spring.datasource.url=jdbc:h2:mem:testdbsa
  6. spring.datasource.driver-class-name=org.h2.Driver
  7. spring.datasource.username=sa
  8. spring.datasource.password=

两种方法都可行, 不同的场景用不同的方式, 太多测试数据的情况下可以用后者.

谢谢大家!


参考

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注