SpringBoot(13)单元测试

十念  金牌会员 | 2022-8-29 03:14:10 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 891|帖子 891|积分 2673

1.单元测试

单元测试(unit test)是为了检验程序的正确性。一个单元可能是单个程序、类、对象、方法 等,它是应用程序的最小可测试部件。
单元测试的必要性如下:

  • 预防Bug。
  • 快速定位Bug。
  • 提高代码质量,减少耦合。
  • 减少调试时间。
  • 减少重构的风险。
2.Spring Boot的测试库

Spring Boot提供了 spring-boot-starter-test启动器。通过它,能引入一些有用的测试库, 如下所示。

  • Spring Test&Spring Boot Test: Spring Boot提供的应用程序功能集成化测试支持。
  • Junit: Java应用程序单元测试标准类库。
  • AssertJ:轻量级的断言类库。
  • Hamcrest:对象匹配器类库。
  • Mockito: Java Mock 测试框架。
  • JsonPath: JSON 操作类库。
  • JSONassert:用于JSON的断言库。
  2.1回归测试框架JUnit

JUnit是对程序代码进行单元测试的Java框架。它用来编写自动化测试工具,降低测试的难度、 减少烦琐性,并有效避免岀现程序错误。
JUnit测试是白盒测试(因为知道测试如何完成功能和完成什么样的功能)。要使用JUnit,则 只需要继承TestCase类。
JUnit提供以下注解。

  • @BeforeClass:在所有测试单元前执行一次,一般用来初始化整体的代码。
  • @AfterClass:在所有测试单元后执行一次,一般用来销毁和释放资源。
  • @Before:在每个测试单元前执行,一般用来初始化方法。
  • @After:任每个测试单元后执行,一股用来回滚测试数据。
  • @Test:辐写测试用例。
  • @Test(timeout=1000):对测试单元进行限时。这里的"1000”表示若超过1s则超时, 测试失败。
  • @Test(expected=Exception.class):指定测试单元期望得到的异常类。如果执行完成后 没有抛出指定的异常,则测试失败。
  • @Ignore:执行测试时将忽略掉此方法。如果用于修饰类,则忽略基个类。
  • @RunWith: 再JUnit中有很多Runner,它们负责调用测试代码。毎个Runner都有特殊功能,应根据需要选择不同的Runner来运行测试代码。
  2.2assertThat

  Unit 4.4结合Hamcrest提供了一个新的断言语法 --------- assertThat。使用assertThat的一个断言语句结合Hamcrest提供的匹配符,就可以表达全部的测试思想。
  (1)assertThat的基本语法如下
  assertThat( [value], [matcher statement] )

  • value:要测试的变量值。
  • matcher statement:如果value值与matcher statement所表达的朗望值相符,则测试
  成功,否则失败。简单地说,就是“两个值逬行比较”
  (2)一般匹配符
  assertThat(testNumber,allof(greaterThan(5),lessThan(8))):allOf 表示,所有条件必须都成立,测试才能通过
  assertThat(testNumber,anyOf(greaterThan(5),lessThan(8))):anyOf 表示,所有 条件只要有一个成立,则测试通过G
  assertThat(testNumber,anything()):anything表示,无论什么条件,结果永远为“true”
  (3)字符串相关匹配符
  assertThat(testNumber,is("buretuzi")):is 表示,如果前面待测的 testString 等于后面给出的String,则测试通过。
  assertThat(testNumber,not("buretuzi")): not 表示,如果前面待测的 String 不等于后面给出的String,则测试通过。
  assertThat(testNumber,containsString("buretuzi")):containsString 表示,如果测试的字符串teststring 子字符串 "buretuzi"  ,则测试通过
  assertThat(testNumber,endWith("zi")):endsWith 表示,如果测试的字符串 teststring以子字符串 "zi" 结尾,则测试通过。
  assertThat(testNumber,startWith("bu")):starts With 表示,如果测试的字符串 teststring以子字符串“bu”开始,则测试通过。
  assertThat(testNumber,equalTo("bu")):equalTo 表示,如果测试的 testValue 等于Value,则测试通过。equalTo可以用来测试数值、字符串和对象。
  assertThat(testNumber,equalToIgnoreCase("bu")):equalToIgnoringCase 表示, 如果测试的字符串teststring在忽略大小写的情况下等于“bu” ,则测试通过。
  assertThat(testNumber,equalToIgnoreWhiteSpace("bu")):equalToIgnoring- WhiteSpace表示,如果测试的字符串teststring在忽略头尾的任意一个空格的情况下等于 “bu”,则测试通过。字符串中的空格不能被忽略。
  (4)数值相关匹配符
  assertThat(testDouble,closeTo(1.0,8.8)):closeTo 表示,如果测试的浮点型数 testDouble在1.0 ~8.8之间,则测试通过。
  assertThat(testNumber,greaterThan(2.0)):greaterThan 表示,如果测试的数值 testNumber大于2.0,则测试通过
  assertThat(testNumber,lessThan(35.0))):lessThan 表示,如果测试的数值 testNumber小于35.0,则测试通过
  assertThat(testNumber,greaterThanOrEqualTo(3.0)):greaterThanOrEqualTo表示,如果测试的数值estNumber大于或等于3.0,则测试通过。
  assertThat(testNumber,lessThanOrEqualTo(3.0)):lessThanOrEqualTo 表示, 如果测试的数值testNumber小于或等于3.0,则测试通过。
  (5)collection相关匹配符
  assertThat(mObject,hasEntry("key","value")):hasEntry 表示,如果测试的 Map 对象mObject含有一个键值为“key”对应元素值为“value”的Entry项,则测试通过
  assertThat(mObject,hasKey("key")):hasKey 表示,如果测试的 Map 对象 mObject 含有键值“key”,则测试通过。
  assertThat(mObject,hasValue("key")):hasVafue 表示,如果测试的 Map 对象 mObject含有元素值“value”,则测试通过。
  assertThat(mObject,hasItem("bu")):hasltem 表示,如果测试的迭代对象 iterableObject含有元素“bu”项,则测试通过。
  2.3.了解 Mockito

  Mockito是GitHub上使用最广泛的Mocking框架。它提供简洁的API用来测试。Mockito简单易学、可读性强、验证语法简洁。
  与JUnit结合使用,Mockito框架可以创建和配置Mock对象。
  2.4了解 JSONPath

  JSONPath是xPath在JSON中的应用。它的数据结构通常不一定有根元素,它用一个抽象的名字来表示最外层对象,而且允许使用通配符“*”表示所有的子元素名和数组素引。
  JSONPath表达式可以使用符号解析JSON,如以下代码:
  $.person.card[0].num
    或使用符号,如以下代码:
  $['person']['card'][0]['num']
  2.5测试的回滚

  在单元测试中可能会产生垃圾数据,可以开启事务功能进行回滚——在方法或类头部添加注解 @Transactional即可。用法见以下代码:
 
  1. package com.itheima.repository;
  2. import com.itheima.domain.Book;
  3. import com.itheima.service.BookService;
  4. import org.junit.Test;
  5. import org.junit.runner.RunWith;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.test.context.junit4.SpringRunner;
  9. import org.springframework.transaction.annotation.Transactional;
  10. @RunWith(SpringRunner.class)
  11. @SpringBootTest
  12. @Transactional
  13. public class CardRepositoryTest {
  14.     @Autowired
  15.     private BookService bookService;
  16.     @Test
  17.     public void testRollback() {
  18.         Book book = new Book();
  19.         book.setUsername("username");
  20.         book.setPassword("password");
  21.         bookService.insert(book);
  22.     }
  23. }
复制代码
  上述代码在类上添加了注解@Transactional,测试完成后就会回滚,不会产生垃圾数据。如果要关闭回滚,则只要加上注解@Rollback(false)即可
  2.6快速创建测试单元

  在Spring Boot中进行单元测试很简单,它已经自动添加好了 Test的Starter依赖,见下方依赖元素:
  1. <dependency>
  2.         <groupId>org.springframework.boot</groupId>
  3.         <artifactId>spring-boot-starter-test</artifactId>
  4.         <scope>test</scope>
  5. </dependency>
复制代码
  创建一个测试类:
  1. package com.itheima;
  2. import org.junit.Test;
  3. import org.junit.runner.RunWith;
  4. import org.springframework.test.context.junit4.SpringRunner;
  5. @RunWith(SpringRunner.class)
  6. public class MyTest {
  7.     @Test
  8.     public void test() throws Exception {
  9.     }
  10. }
复制代码
代码解释如下。

  • @SpringBootTest:是Spring Boot用于测试的注解,可指定入口类或测试环境等。
  • @RunWith(SpringRunner.class):让测试运行于 Spring 的测试环境。
  • @Test:表示为一个测试单元。在要测试的方法上加注解@Test,然后鼠标右击“Run” (或单击其左边的绿色三角箭头)即可进行测试。
除用这种方式创建测试单元外,还可以通过IDEA的快捷键快速完成创建。
在IDEA中,快速创建测试单元主要有以下3种方式:

  • 通过快捷键Ctrl+Shift+T (在Windows系统中)来创建测试。
  • 单击菜单栏中的"NavigatoLTest”命令。
  • 在方法处单击鼠标右键,在弹出的菜单中选择“GoTo-Test"命令。
  2.7 Controller层的单元测试

  (1)创建一个用于测试的控制器
  1. package com.itheima.controller;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. import org.springframework.web.bind.annotation.RestController;
  4. @RestController
  5. public class JunitController {
  6.     @RequestMapping("/hello")
  7.     public String hello(String name){
  8.         return "hello " + name;
  9.     }
  10. }
复制代码
  代码解释如下:

  • @RestController:代表这个类是REST风格的控制器,返回JSON/XML类型的数据。
  • @RequestMapping:用于配置URL和方法之间的映射。可用在类和方法上。用在方法上, 则其路径会继承用在类上的路径。
  (2)编写测试
  1. package com.itheima;
  2. import org.junit.Assert;
  3. import org.junit.Before;
  4. import org.junit.Test;
  5. import org.junit.runner.RunWith;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.http.MediaType;
  9. import org.springframework.test.context.junit4.SpringRunner;
  10. import org.springframework.test.web.servlet.MockMvc;
  11. import org.springframework.test.web.servlet.MvcResult;
  12. import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
  13. import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
  14. import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
  15. import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  16. import org.springframework.web.context.WebApplicationContext;
  17. @SpringBootTest
  18. @RunWith(SpringRunner.class)
  19. public class HelloControllerTest {
  20.     //启用web上下文
  21.     @Autowired
  22.     private WebApplicationContext webApplicationContext;
  23.     private MockMvc mockMvc;
  24.     @Before
  25.     public void setUp() throws Exception {
  26.         //使用上下文构建MockMvc
  27.         mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
  28.     }
  29.     @Test
  30.     public void testHello() throws Exception {
  31.         //得到MvcResult自定义验证,执行请求
  32.         MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/hello")
  33.                 //发送post请求
  34.                 .contentType(MediaType.APPLICATION_JSON_UTF8)
  35.                 //传入参数
  36.                 .param("name","buretuzi")
  37.                 //接收的类型
  38.                 .accept(MediaType.APPLICATION_JSON_UTF8))
  39.                 //判断接收到的状态是否是200,等同于Assert.assertEquals(200, status);
  40.                 .andExpect(MockMvcResultMatchers.status().isOk())
  41.                 //等同于Assert.assertEquals("hello buretuzi",content);
  42.                 .andExpect(MockMvcResultMatchers.content().string("hello buretuzi"))
  43.                 .andDo(MockMvcResultHandlers.print())
  44.                 //返回MockMvc
  45.                 .andReturn();
  46.         int status = mvcResult.getResponse().getStatus();
  47.         String content = mvcResult.getResponse().getContentAsString();
  48.         Assert.assertEquals(200, status);
  49.         Assert.assertEquals("hello buretuzi",content);
  50.     }
  51. }
复制代码
代码解释如下:

  • @SpringBootTest:是Spring Boot用于测试的注解,可指定入口类或测试环境等。
  • @RunWith(SpringRunner.class):让测试运行于 Spring 的测试环境。
  • @Test:表示一个测试单元。
  • WebApplicationContext:启用Web上下文,用于获取Bean中的内容。
  • @Before:表示在测试单元执行前执行。这里使用上下文构建MockMvc
  • get:指定请求方式是GET。一般用浏览器打开网页就是GET方式。
  运行测试,在控制器中会输出以下结果:
  1. MockHttpServletRequest:
  2.       HTTP Method = GET
  3.       Request URI = /hello
  4.        Parameters = {name=[buretuzi]}
  5.           Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json;charset=UTF-8"]
  6.              Body = null
  7.     Session Attrs = {}
  8. Handler:
  9.              Type = com.itheima.controller.JunitController
  10.            Method = com.itheima.controller.JunitController#hello(String)
  11. Async:
  12.     Async started = false
  13.      Async result = null
  14. Resolved Exception:
  15.              Type = null
  16. ModelAndView:
  17.         View name = null
  18.              View = null
  19.             Model = null
  20. FlashMap:
  21.        Attributes = null
  22. MockHttpServletResponse:
  23.            Status = 200
  24.     Error message = null
  25.           Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"14"]
  26.      Content type = application/json;charset=UTF-8
  27.              Body = hello buretuzi
  28.     Forwarded URL = null
  29.    Redirected URL = null
  30.           Cookies = []
复制代码
  2.8 Service层的单元测试

  (1)创建实体类
  1. package com.itheima.domain;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. @Data
  6. @AllArgsConstructor
  7. @NoArgsConstructor
  8. public class User {
  9.     private long id;
  10.     private String name;
  11.     private int age;
  12. }
复制代码
  (2)创建服务类
  这里用@Service来标注服务类,并实例化了一个User对象,见以下代码。
  1. package com.itheima.service;
  2. import com.itheima.domain.User;
  3. import org.springframework.stereotype.Service;
  4. @Service
  5. public class UserService {
  6.     public User getUser(){
  7.         User user = new User();
  8.         user.setId(1);
  9.         user.setAge(13);
  10.         user.setName("八嘎");
  11.         return user;
  12.     }
  13. }
复制代码
  (3)编写测试
  编写测试用于比较实例化的实体User和测试预期值是否一样,见以下代码:
  1. package com.itheima;
  2. import com.itheima.domain.User;
  3. import com.itheima.service.UserService;
  4. import org.junit.Assert;
  5. import org.junit.Test;
  6. import org.junit.runner.RunWith;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.boot.test.context.SpringBootTest;
  9. import org.springframework.test.context.junit4.SpringRunner;
  10. import static org.hamcrest.Matchers.is;
  11. @RunWith(SpringRunner.class)
  12. @SpringBootTest
  13. public class UserServiceTest {
  14.     @Autowired
  15.     private UserService userService;
  16.     @Test
  17.     public void getUserinfo() {
  18.         User user = userService.getUser();
  19.         Assert.assertEquals(13,user.getAge());
  20.         Assert.assertThat(user.getName(), is("嘎"));
  21.     }
  22. }
复制代码
   运行测试,结果显示出错,表示期望的值和实际值不一样,如下所示。
  java.lang.AssertionError:  
  Expected: is "嘎"  
    but: was "八嘎"
  
  2.9 Repository层的单元测试
  1. package com.itheima;
  2. import com.itheima.domain.Book;
  3. import com.itheima.service.BookService;
  4. import org.junit.Test;
  5. import org.junit.runner.RunWith;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.test.context.junit4.SpringRunner;
  9. import org.springframework.transaction.annotation.Transactional;
  10. import java.util.List;
  11. @RunWith(SpringRunner.class)
  12. @SpringBootTest
  13. @Transactional
  14. public class BookRepositoryTest {
  15.     @Autowired
  16.     private BookService bookService;
  17.     @Test
  18.     public void testQuery(){
  19.         List<Book> books = (List<Book>) bookService.findAll();
  20.         for (Book book : books) {
  21.             System.out.println(book.getUsername());
  22.         }
  23.     }
  24.     @Test
  25.     public void testRollback(){
  26.         Book book = new Book();
  27.         book.setUsername("干净又卫生");
  28.         bookService.insert(book);
  29.     }
  30. }
复制代码
代码解释如下。

  • @Transactional:即回滚的意思。所有方法执行完之后,回滚成原来的样子。
  • testRollBank方法:执行添加一条记录。如果开启了@Transactional,则会在添加之后进行回滚,删除刚添加的数据,如果注释掉@Transactional, 则完成添加后不回滚。在测试时可以尝试去掉和添加@Transactional状态下的不同效果。这里的@Transactional 放在类上,也可以加在方法上以作用于方法。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

十念

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表