0


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

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

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

什么是单元测试?

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

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

单元测试的必要性?

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

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

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

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

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

单元测试的意义

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

JUnit使用

Controller层单元测试

这里我们以Springboot为例:

1、引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

2、代码案例

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MainApplication.class)
public class StudentControllerTest {
    
    // 注入Spring容器
    @Autowired
    private WebApplicationContext applicationContext;
    // 模拟Http请求
    private MockMvc mockMvc;

    @Before
    public void setupMockMvc(){
        // 初始化MockMvc对象
        mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
    }
    
    /**
     * 新增学生测试用例
     * @throws Exception
     */
    @Test
    public void addStudent() throws Exception{
        String json="{"name":"张三","className":"三年级一班","age":"20","sex":"男"}";
        mockMvc.perform(MockMvcRequestBuilders.post("/student/save")    //构造一个post请求
                    // 发送端和接收端数据格式
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .content(json.getBytes())
            )
           // 断言校验返回的code编码
           .andExpect(MockMvcResultMatchers.status().isOk())
           // 添加处理器打印返回结果
           .andDo(MockMvcResultHandlers.print());
    }
}

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

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

@Autowired
private StudentController studentController;
@Before
public void setupMockMvc(){
   // 初始化MockMvc对象
   mockMvc = MockMvcBuilders.standaloneSetup(studentController).build();
}

其中 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层单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentServiceTest {

    @Autowired
    private StudentService studentService;

    @Test
    public void getOne() throws Exception {
         Student stu = studentService.selectByKey(5);
         Assert.assertThat(stu.getName(),CoreMatchers.is("张三"));
    }
}

执行结果

DAO层单元测试

代码示例

@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentDaoTest {

    @Autowired
    private StudentMapper studentMapper;

    @Test
    @Rollback(value = true)
    @Transactional
    public void insertOne() throws Exception {
         Student student = new Student();
         student.setName("李四");
         student.setMajor("计算机学院");
         student.setAge(25);
         student.setSex('男');
         int count = studentMapper.insert(student);
         Assert.assertEquals(1, count);
    }
}

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

异常测试

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

public void computeScore() {
   int a = 10, b = 0;
   int c = a/b;
}

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

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

执行单元测试也会通过,原因是 @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 接口,含义是借书接口,暂且不做实现

public interface BookService {
    Book orderBook(String name);
}

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

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

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

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

4、执行结果

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

标签: junit 单元测试 java

本文转载自: https://blog.csdn.net/crg18438610577/article/details/129241884
版权归原作者 程序员万万 所有, 如有侵权,请联系我们删除。

“Java单元测试浅析(JUnit+Mockito)”的评论:

还没有评论