关键词搜索

源码搜索 ×
×

Java单元测试浅析(JUnit+Mockito)

发布2023-02-27浏览703次

详情内容

Java测试我们应该都遇到过,一般我们会被要求做单元测试,来验证我们代码的功能以及效率。

这里来和大家一起探讨下有关单于测试。

什么是单元测试?

是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

经常与单元测试联系起来的另外一些开发活动包括代码走读(Code review),静态分析(Static analysis)和动态分析(Dynamic analysis)。静态分析就是对软件的源代码进行研读,查找错误或收集一些度量数据,并不需要对代码进行编译和执行。动态分析就是通过观察软件运行时的动作,来提供执行跟踪,时间分析,以及测试覆盖度方面的信息。

单元测试的必要性?

公司要求我们写单元测试,我们会按照公司要求的单元测试率,来进行相关工作的完成;

那么会出现这样一些情况:

  • 感觉写单元测试费力不讨好,有时候写单元测试的时间大于写业务逻辑的时间,需要 mock 一大堆数据。要保证各种覆盖率,特别是分支覆盖率,需要覆盖到所写的每一个分支。
  • 大家写的单元测试质量参差不齐,因为感觉大家都是为了写测试而写测试,而不是真正的 tdd 
  • 迭代频繁,节奏紧凑的环境下,想要做到真正的 tdd ,还是有很大的难度的

确实,这是一个比较常见的问题。

那么我觉得,对于公司而言,需要有一套相关的单元测试规范,哪些是必须要去写单元测试的,分高低的优先级。同时对于单元测试案例,要做到及时的审查,关注其覆盖率,关注其有效性,对于无效的测试要及时剔除和优化。

单元测试的意义

程序代码都是由基本单元不断组合成复杂的系统,底层基本单元都无法保证输入输出正确性,层级递增时,问题就会不断放大,直到整个系统崩溃无法使用。所以单元测试的意义就在于保证基本功能是正常可用且稳定的。而对于接口、数据源等原因造成的不稳定因素,是外在原因,不在单元测试考虑范围之内。

JUnit使用

Controller层单元测试

这里我们以Springboot为例:

1、引入依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-test</artifactId>
  4. <scope>test</scope>
  5. </dependency>

2、代码案例

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest(classes = MainApplication.class)
  3. public class StudentControllerTest {
  4. // 注入Spring容器
  5. @Autowired
  6. private WebApplicationContext applicationContext;
  7. // 模拟Http请求
  8. private MockMvc mockMvc;
  9. @Before
  10. public void setupMockMvc(){
  11. // 初始化MockMvc对象
  12. mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
  13. }
  14. /**
  15. * 新增学生测试用例
  16. * @throws Exception
  17. */
  18. @Test
  19. public void addStudent() throws Exception{
  20. String json="{"name":"张三","className":"三年级一班","age":"20","sex":""}";
  21. mockMvc.perform(MockMvcRequestBuilders.post("/student/save") //构造一个post请求
  22. // 发送端和接收端数据格式
  23. .contentType(MediaType.APPLICATION_JSON_UTF8)
  24. .accept(MediaType.APPLICATION_JSON_UTF8)
  25. .content(json.getBytes())
  26. )
  27. // 断言校验返回的code编码
  28. .andExpect(MockMvcResultMatchers.status().isOk())
  29. // 添加处理器打印返回结果
  30. .andDo(MockMvcResultHandlers.print());
  31. }
  32. }

只需要在类或者指定方法上右键执行即可,可以直接充当 postman 工作访问指定 url,且不需要写请求代码,

本案例中构造 mockMVC 对象时,也可以使用如下方式:

  1. @Autowired
  2. private StudentController studentController;
  3. @Before
  4. public void setupMockMvc(){
  5. // 初始化MockMvc对象
  6. mockMvc = MockMvcBuilders.standaloneSetup(studentController).build();
  7. }

其中 MockMVC 是 Spring 测试框架提供的用于 REST 请求的工具,是对 Http 请求的模拟,无需启动整个模块就可以对 Controller 层进行调用,速度快且不依赖网络环境。

使用 MockMVC 的基本步骤如下:

  1. mockMvc.perform 执行请求

  2. MockMvcRequestBuilders.post 或 get 构造请求

  3. MockHttpServletRequestBuilder.param 或 content 添加请求参数

  4. MockMvcRequestBuilders.contentType 添加请求类型

  5. MockMvcRequestBuilders.accept 添加响应类型

  6. ResultActions.andExpect 添加结果断言

  7. ResultActions.andDo 添加返回结果后置处理

  8. ResultActions.andReturn 执行完成后返回相应结果

Service层单元测试

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class StudentServiceTest {
  4. @Autowired
  5. private StudentService studentService;
  6. @Test
  7. public void getOne() throws Exception {
  8. Student stu = studentService.selectByKey(5);
  9. Assert.assertThat(stu.getName(),CoreMatchers.is("张三"));
  10. }
  11. }

执行结果

DAO层单元测试

代码示例

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class StudentDaoTest {
  4. @Autowired
  5. private StudentMapper studentMapper;
  6. @Test
  7. @Rollback(value = true)
  8. @Transactional
  9. public void insertOne() throws Exception {
  10. Student student = new Student();
  11. student.setName("李四");
  12. student.setMajor("计算机学院");
  13. student.setAge(25);
  14. student.setSex('男');
  15. int count = studentMapper.insert(student);
  16. Assert.assertEquals(1, count);
  17. }
  18. }

 

 其中 @Rollback (value = true) 可以执行单元测试之后回滚所新增的数据,保持数据库不产生脏数据。

异常测试

1、首先我们在service层可以定义一个异常情况

  1. public void computeScore() {
  2. int a = 10, b = 0;
  3. int c = a/b;
  4. }

2、我们在service中编写单元测试

  1. @Test(expected = ArithmeticException.class)
  2. public void computeScoreTest() {
  3. studentService.computeScore();
  4. }

执行单元测试也会通过,原因是 @Test 注解中的定义了异常

 查看单元测试的覆盖率

1) 单测覆盖率

测试覆盖率是衡量测试过程工作本身的有效性,提升测试效率和减少程序 bug,提升产品可靠性与稳定性的指标。

统计单元测试覆盖率的意义:

1) 可以洞察整个代码中的基础组件功能的所有盲点,发现相关问题。

2) 提高代码质量,通常覆盖率低表示代码质量也不会太高,因为单测不通过本来就映射出考虑到各种情况不够充分。

3) 从覆盖率的达标上可以提高代码的设计能力。

(2) 在 idea 中查看单元测试覆盖率很简单,只需按照图中示例的图标运行,或者在单元测试方法或类上右键 Run 'xxx' with Coverage 即可。执行结果是一个表格,列出了类、方法、行数、分支覆盖情况。

(3) 在代码中会标识出覆盖情况,绿色的是已覆盖的,红色的是未覆盖的。

(4) 如果想要导出单元测试的覆盖率结果,可以使用如下图所示的方式,勾选 Open generated HTML in browser

 

导出结果:

 

JUnit插件自动生成测试案例

1) 安装插件,重启 idea 生效

(2) 配置插件

 

(3) 使用插件

在需要生成单测代码的类上右键 generate...,如下图所示。

生成结果:

单元测试工具Mockito

简介

Mockito 是一个针对 Java 的 mocking 框架。它与 EasyMock 和 jMock 很相似,但是通过在执行校验什么已经被调用,它消除了对期望行为(expectations)的需要。其它的 mocking 库需要你在执行记录期望行为(expectations),而这导致了丑陋的初始化代码。

Mock 过程的使用前提:

(1) 实际对象时很难被构造出来的

(2) 实际对象的特定行为很难被触发

(3) 实际对象可能当前还不存在,比如依赖的接口还没有开发完成等等。

Mockito 官网:https://site.mockito.org 。Mockito 和 JUnit 一样是专门针对 Java 语言的 mock 数据框架,它与同类的 EasyMock 和 jMock 功能非常相似,但是该工具更加简单易用。

Mockito 的特点:

(1) 可以模拟类不仅仅是接口

(2) 通过注解方式简单易懂

(3) 支持顺序验证

(4) 具备参数匹配器

使用案例

1、在之前的代码中在定义一个 BookService 接口,含义是借书接口,暂且不做实现

  1. public interface BookService {
  2. Book orderBook(String name);
  3. }

2、在之前的 StudentService 类中新增一个 orderBook 方法,含义是学生预定书籍方法,其中实现内容调用上述的 BookService 的 orderBook 方法。

  1. public Book orderBook(String name) {
  2. return bookService.orderBook(name);
  3. }

 3、编写单元测试方法,测试 StudentService 的 orderBook 方法

  1. @Test
  2. public void orderBookTest() {
  3. Book expectBook = new Book(1L, "钢铁是怎样炼成的", "书架A01");
  4. Mockito.when(bookService.orderBook(any(String.class))).thenReturn(expectBook);
  5. Book book = studentService.orderBook("");
  6. System.out.println(book);
  7. Assert.assertTrue("预定书籍不符", expectBook.equals(book));
  8. }

4、执行结果

 

上述内容并没有实现 BookService 接口的 orderBook (String name) 方法。但是使用 mockito 进行模拟数据之后,却通过了单元测试,原因就在于 Mockito 替换了本来要在 StudentService 的 orderBook 方法中获取的对象,此处就模拟了该对象很难获取或当前无法获取到,用模拟数据进行替代。

 

相关技术文章

点击QQ咨询
开通会员
返回顶部
×
微信扫码支付
微信扫码支付
确定支付下载
请使用微信描二维码支付
×

提示信息

×

选择支付方式

  • 微信支付
  • 支付宝付款
确定支付下载