Spring Boot 2.0 读书笔记_13:单元测试【白盒测试】上

9.单元测试【白盒测试】

  • JUnit:Java语言编写的开源的回归测试框架
相关概念 解释
测试 @Test 注解定义的测试方法,为了运行这个方法,
JUnit会创建一个包含类的新实例,然后在调用这个被注释的方法。
测试类 包含多个 @Test注释方法的容器。
断言 Assert 断言,为了进行验证,使用由JUnit的Assert类提供的assert方法。
常用的assert方法:
1. assertEquals():用来查看对象中存放的值是否是期望值
2. assertFalse()assertTrue():用来查看变量是否为false或true
3.assertSame()assertNotSame():用来比较两个对象的引用是否相等和不相等
4.assertNull()assertNotNull():用来查看对象是否为空和不为空
测试集 Suite 是包含不同测试的容器,将多个相关的测试类归为一组,一起运行。
@RunWith(Suite.class)
@SuiteClasses({Test1.class, Test2.class})
public void TestSuitMain{
//将Test1和Test2中的测试用例均执行一遍
}
测试运行器 Runner 管理测试类的生命周期,用来运行测试。JUnit4是支持向后兼容的。
JUnit 自带的几个测试运行器都继承了 org.junit.runner.Runner 类。

JUnit 3.x 版本通过测试方法的命名(test+方法名)来确定是否进行测试,并且所有的测试类都必须继承TestCase。JUnit 4.x 版本全面引入了注解来执行测试用例。BeforeClassAfterClassAfterBeforeTestIgnore。其中,BeforeClassAfterClass 在每个类加载开始和结束时运行,需要设置静态 static 方法。BeforeAfter 需要在每个测试方法开始之前和结束之后运行。

@BeforeClass
public static void beforeClassTest() {
	System.out.println("单元测试开始之前执行初始化【静态方法】");
}

@Before
public void beforeTest() {
	System.out.println("单元测试开始之前执行");
}

@Test
public void test() {
	System.out.println("单元测试执行");
}

@After
public void afterTest() {
	System.out.println("单元测试结束之后执行");
}

@AfterClass
public static void afterClassTest() {
	System.out.println("单元测试结束之后执行【静态方法】");
}

注:

1. JUnit在执行每个@Test方法之前,会为测试类创建一个新的实例。保证测试方法之间的独立性,避免在测试代码中产生意外的副作用。

2.JUnit的入口函数:JUnitCore.class中包含main方法。[org.junit.runner]

  • Spring Boot单元测试

    Spring Boot 提供了一些使用程序和注解,用来帮助我们测试应用程序。测试由两个模块支持:spring-boot-test[核心项目]spring-boot-test-autoconfigure[自动配置测试]

    开发时通常引入 spring-boot-starter-test 依赖:

      
      	org.springframework.boot
      	spring-boot-starter-test
      
    

    该依赖提供了以下测试库:

测试库 简介
JUnit 标准的单元测试Java应用程序
Spring Test & Spring Boot Test 针对Spring Boot应用的单元测试
Mockito Java Mocking框架,用于模拟任何Spring管理的Bean
AssertJ 流畅的assertion库,提供更多期望值与返回值的方法比较方式
Hamcrest 库的匹配对象
JSONassert 对JSON对象或者JSON字符串断言的库
JsonPath 类似于XPath在xml文档中的定位,JsonPath表达式通常是用来路径检索或设置Json的。

Spring Boot测试脚手架举例:Spring Boot使用一系列注解来增强单元测试,其基本模板如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestClass {
	...
}

@RunWith 是JUnit标准注解,设置单元测试框架不使用内置默认方式进行,而是使用 @RunWith 注解指明的类提供单元测试,所有的 Spring 单元测试总是使用 SpringRunner.class

@SpringBootTest 注解用于 Spring Boot 应用测试,该注解会根据包名逐级往上找,一直找到 Spring Boot 主程序 [通过 @SpringBootApplication 来判断是否是主程序],并在单元测试的时候启动该类创建 Spring 上下文环境。

Spring 单元测试并不会在每个单元测试方法前都启动一个全新的 Spring 上下文,Spring 单元测试会缓存上下文环境,提供给每个单元测试方法。如果单元测试方法改变了上下文 [更改了Bean定义],此时就需要在单元测试方法添加 @DirtiesContext 注解以提示 Spring 重新加载 Spring 上下文。


测试场景举例:

  • Service

    常规的Service层测试是通过 Controller层 调用 Service层进行测试,这里需要注意几点:

    • 单元测试需要保证可重复测试,因此希望Service测试完毕后,数据可以自动回滚。
    • Service 依赖的其他未开发完毕模块的时候,测试该怎样进行?
    • 大多数 Service 对接的是数据层,如何模拟数据测试场景?

    上述问题解决方案如下:

    • Spring Boot单元测试默认会在单元测试方法运行结束后进行 事务回滚
    • 既然依赖了未完成[不存在]模块,那么我们将采用上述 Mockito库 来模拟注入未完成对象。
    • Spring 引入了 @sql注解,在测试之前执行一系列的SQL脚本进行数据初始化工作。

    举栗子:

      @RunWith(SpringRunner.class)
      @SpringBootTest
      @Transactional 
      public class UserServiceTest {
      
      	@Autowired
      	UserService userService;
      
      	@MockBean
      	private CreditSystemService creditSystemService;
      
      	@Test
      	public void testService() {
      		int userId = 10;
      		int expectedCredit = 100;
      		given(this.creditSystemService.getUserCredit(anyInt())).willReturn(expectedCredit);
      		// 测试 UserService 的 getCredit 获取用户积分
      		int credit = userService.getCredit(10);
      		// 断言方法判断返回值与期望值
      		assertEquals(expectedCredit, credit);
      	}
      }
    

    贴上UserService接口实现类代码:

      @Service
      @Transactional
      public class UserServiceImpl implements UserService {
      
      	@Autowired
      	CreditSystemService creditSystemService;
      	
      	@Autowired
      	UserDao userDao;
      	
      	@Override
      	public int getCredit(int userId) {
      		User user = userDao.single(userId);
      		if(user != null) {	
      			return creditSystemService.getUserCredit(userId);
      		} else {
      			return -1;
      		}
      	}
      }
    

    由于单元测试不能实际调用 creditSystemService,因此,在单元测试类中使用了 @MockBean 注解。

      @MockBean
      private CreditSystemService creditSystemService;
    

    @MockBean 注解,可以自动注入 Spring 管理的 Service [Bean],用来提供模式实现。因此这里的 creditSystemService 对象实际上并不是 CreditSystemServiceImpl 的实现实例,而是一个通过 Mockito工具创建的 CreditSystemService M o c k i t o M o c k MockitoMock MockitoMockxxxxxx 实例。

    given方法是 Mockito 的一个静态方法,用来模拟一个Service方法调用返回,anyInt() 表示可以传入任何参数,willReturn() 方法说明该调用将返回其方法参数值。

      given(this.creditSystemService.getUserCredit(anyInt())).willReturn(expectedCredit);
    

    上述语句表示:使用模拟对象 creditSystemService 调用 getUserCredit() 方法,无论传入参数如何,都将返回 expectedCredit 的值。

    注:默认情况下,单元测试完毕,事务会进行回滚。有时需要查看数据库测试结果而不希望事务回滚。可以在方法上使用 @Rollback(false) 即可。

  • Controller

    Spring Boot可以单独测试 Controller 代码,用来验证与 Controller 相关的 URL路径映射、文件上传、参数绑定、参数校验等特性。以上特性均通过 @WebMvcTest 来完成单元测试。

      @RunWith(SpringRunner.class)
      @WebMvcTest(UserController.class)
      public class UserControllerTest {
    
      	@Autowired
      	private MockMvc mvc;
      	
      	@MockBean
      	UserService userService;
      
      	@Test
      	public void testMVC() throws Exception {
      		int userId = 10;
      		int expectedCredit = 100;
      		//模拟userService
      		given(this.userService.getCredit(anyInt())).willReturn(100);
      		//http 调用
      		mvc.perform(get("/user/{id}", userId)).andExpect(content().string(String.valueOf(expectedCredit)));
      	}
      }
    

    @WebMvcTest 表示这是一个MVC测试,其参数可以传入多个待测试的 Controller 类。

    MockMvc 是 Spring 提供的专门用于测试的 Spring MVC 类。

    @MockBean 用来模拟UserController调用的所有Service。[Spring MVC 测试中,Spring容器并不会初始化 @Service@Component 注解的类]

    perform 表示完成一次MVC调用,Spring MVC Test 是 Servlet 容器内的一种模拟测试,实际上并不会发起正在的 HTTP 调用。

    get() 方法模拟了一次 get 请求,参数站位符 { } 对应 userId。

    andExpect() 表示请求期望的返回结果。

  • 请求模拟

    关于 Spring 专门提供的用于测试的 Spring MVC 类:MockMvc,其核心方法为:perform

      public ResultActions perform(RequestBuilder requestBuilder)
    

    RequestBuilder 类可以通过使用 MockMvcRequestBuilders 的 get、post、Multipart等方法进行实现请求模拟。以下是常用的例子:

模拟请求类型 代码实现
模拟get请求 mvc.perform(get("/test/{id}", 77));
模拟post请求 mvc.perform(post("/test/{id}", 77));
模拟提交复选框内容 mvc.perform(get("/test/{id}", 77).param(“job”, “IT”, “gov”));
模拟提交键值对参数 mvc.perform(get("/test/{id}", 77).param(“msg”, “hello world”));
模拟Session和Cookie mvc.perform(get("/user.html").sessionAttr(name, value));
mvc.perform(get("/user.html").cookie(new Cookie(name, value))
模拟提交Json String json = … ;
mvc.perform(get("/user.html").content(json));
模拟提交HTTP Header mvc.perform(get("/test/{id}", 77).
contentType(“application/x-www-form-urlencoded”).
accept(“application/json”).
header(header, value));
  • 请求结果返回比较

    通过上述描述我们知道:mockMvc 该类的核心方法 perform 返回值为 ResultActions 实例。该实例代表了 MVC 模拟请求调用的结果,并提供一系列的 addExpect() 方法来对结果进行比较。

      mvc.perform(get("/user/{id}", userId))
         .andExpect(content().string(String.valueOf(expectedCredit)));
    

    上述代码中表示:期望返回的内容为与设定值相符。

    相关API查看可见:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/web/servlet/ResultActions.html

你可能感兴趣的:(代码笔记,后端技术学习,Spring,Boot,2.0,读书笔记)