首页 > 编程语言 >Java单元测试浅析(JUnit+Mockito)

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

时间:2023-12-07 19:15:30浏览次数:43  
标签:String Mockito 单元测试 class public orderBook 测试 浅析

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

作者:京东物流 秦彪

1. 什么是单元测试

(1)单元测试环节:

测试过程按照阶段划分分为:单元测试、集成测试、系统测试、验收测试等。相关含义如下:

1) 单元测试: 针对计算机程序模块进行输出正确性检验工作。

2) 集成测试: 在单元测试基础上,整合各个模块组成子系统,进行集成测试。

3) 系统测试: 将整个交付所涉及的协作内容都纳入其中考虑,包含计算机硬件、软件、接口、操作等等一系列作为一个整体,检验是否满足软件或需求说明。

4) 验收测试: 在交付或者发布之前对所做的工作进行测试检验。

单元测试是阶段性测试的首要环节,也是白盒测试的一种,该内容的编写与实践可以前置在研发完成,研发在编写业务代码的时候就需要生成对应代码的单元测试。单元测试的发起人是程序设计者,受益人也是编写程序的人,所以对于程序员,非常有必要形成自我约束力,完成基本的单元测试用例编写。

(2)单元测试特征:

由上可知,单元测试其实是针对软件中最小的测试单元来进行验证的。这里的单元就是指相关的功能子集,比如一个方法、一个类等。值得注意的是作为最低级别的测试活动,单元测试验证的对象仅限于当前测试内容,与程序其它部分内容相隔离,总结起来单元测试有以下特征:

1) 主要功能是证明编写的代码内容与期望输出一致。

2) 最小最低级的测试内容,由程序员自身发起,保证程序基本组件正常。

3) 单元测试尽量不要区分类与方法,主张以过程性的方法为测试单位,简单实用高效为目标。

4) 不要偏离主题,专注于测试一小块的代码,保证基础功能。

5) 剥离与外部接口、存储之间的依赖,使单元测试可控。

6) 任何时间任何顺序执行单元测试都需要是成功的。

2. 为什么要单元测试

(1)单元测试意义:

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

(2)使用 main 方法进行测试:

@PostMapping(value="/save")
public Map<String,Object> save(@RequestBody Student stu) {
    studentService.save(stu);
    Map<String,Object> params = new HashMap<>();
    params.put("code",200);
    params.put("message","保存成功");
    return params;
}

假如要对上面的 Controller 进行测试,可以编写如下的代码示例,使用 main 方法进行测试的时候,先启动整个工程应用,然后编写 main 方法如下进行访问,在单步调试代码。

public static void main(String[] args) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        String json = "{"name":"张三","className":"三年级一班","age":"20","sex":"男"}";
        HttpEntity<String> httpEntity = new HttpEntity<>(json, headers);
        String url = "http://localhost:9092/student/save";
        MainMethodTest test = new MainMethodTest();
        ResponseEntity<Map> responseEntity = test.getRestTemplate().postForEntity(url, httpEntity, Map.class);
        System.out.println(responseEntity.getBody());
    }

(3)使用 main 方法进行测试的缺点:

1) 通过编写大量的 main 方法针对每个内容做打印输出到控制台枯燥繁琐,不具备优雅性。

2) 测试方法不能一起运行,结果需要程序员自己判断正确性。

3) 统一且重复性工作应该交给工具去完成。

3. 单元测试框架 - JUnit

3.1 JUnit 简介

JUnit 官网:https://junit.org/。JUnit 是一个用于编写可重复测试的简单框架。它是用于单元测试框架的 xUnit 体系结构的一个实例。

JUnit 的特点:

(1) 针对于 Java 语言特定设计的单元测试框架,使用非常广泛。

(2) 特定领域的标准测试框架。

(3) 能够在多种 IDE 开发平台使用,包含 Idea、Eclipse 中进行集成。

(4) 能够方便由 Maven 引入使用。

(5) 可以方便的编写单元测试代码,查看测试结果等。

JUnit 的重要概念:

名称功能作用
Assert断言方法集合
TestCase表示一个测试案例
TestSuite包含一组 TestCase,构成一组测试
TestResult收集测试结果

JUnit 的一些注意事项及规范:

(1) 测试方法必须使用 @Test 修饰

(2) 测试方法必须使用 public void 进行修饰,不能带参数

(3) 测试代码的包应该和被测试代码包结构保持一致

(4) 测试单元中的每个方法必须可以独立测试,方法间不能有任何依赖

(5) 测试类一般使用 Test 作为类名的后缀

(6) 测试方法使一般用 test 作为方法名的前缀

JUnit 失败结果说明:

(1) Failure:测试结果和预期结果不一致导致,表示测试不通过

(2) error:由异常代码引起,它可以产生于测试代码本身的错误,也可以是被测代码的 Bug

3.2 JUnit 内容

(1) 断言的 API

断言方法断言描述
assertNull(String message, Object object)检查对象是否为空,不为空报错
assertNotNull(String message, Object object)检查对象是否不为空,为空报错
assertEquals(String message, Object expected, Object actual)检查对象值是否相等,不相等报错
assertTrue(String message, boolean condition)检查条件是否为真,不为真报错
assertFalse(String message, boolean condition)检查条件是否为假,为真报错
assertSame(String message, Object expected, Object actual)检查对象引用是否相等,不相等报错
assertNotSame(String message, Object unexpected, Object actual)检查对象引用是否不等,相等报错
assertArrayEquals(String message, Object[] expecteds, Object[] actuals)检查数组值是否相等,遍历比较,不相等报错
assertArrayEquals(String message, Object[] expecteds, Object[] actuals)检查数组值是否相等,遍历比较,不相等报错
assertThat(String reason, T actual, Matcher<? super T> matcher)检查对象是否满足给定规则,不满足报错

(2) JUnit 常用注解:

1) @Test: 定义一个测试方法 @Test (excepted=xx.class): xx.class 表示异常类,表示测试的方法抛出此异常时,认为是正常的测试通过的 @Test (timeout = 毫秒数) : 测试方法执行时间是否符合预期。

2) @BeforeClass: 在所有的方法执行前被执行,static 方法全局只会执行一次,而且第一个运行。

3) @AfterClass:在所有的方法执行之后进行执行,static 方法全局只会执行一次,最后一个运行。

4) @Before:在每一个测试方法被运行前执行一次。

5) @After:在每一个测试方法运行后被执行一次。

6) @Ignore:所修饰的测试方法会被测试运行器忽略。

7) @RunWith:可以更改测试执行器使用 junit 测试执行器。

3.3 JUnit 使用

3.3.1 Controller 层单元测试

(1) Springboot 中使用 maven 引入 Junit 非常简单,使用如下依赖即可引入:

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

(2) 上面使用 main 方法案例可以使用如下的 Junit 代码完成:

@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,且不需要写请求代码,这些都由工具自动完成。


(3)案例中相关组件介绍

本案例中构造 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 执行完成后返回相应结果

3.3.2 Service 层单元测试

可以编写如下代码对 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("张三"));
}

}

执行结果:



3.3.3 Dao 层单元测试

可以编写如下代码对 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) 可以执行单元测试之后回滚所新增的数据,保持数据库不产生脏数据。

3.3.4 异常测试

(1) 在 service 层定义一个异常情况:

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

(2) 在 service 的测试类中定义单元测试方法:

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

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



3.3.5 测试套件测多个类

(1) 新建一个空的单元测试类

(2) 利用注解 @RunWith (Suite.class) 和 @SuiteClasses 标明要一起单元测试的类

@RunWith(Suite.class)
@Suite.SuiteClasses({ StudentServiceTest.class, StudentDaoTest.class})
public class AllTest {
}

运行结果:



3.3.6 idea 中查看单元测试覆盖率

(1) 单测覆盖率

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

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

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

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

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

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



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



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



导出结果:



3.3.7 JUnit 插件自动生成单测代码

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



(2) 配置插件



(3) 使用插件

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



生成结果:



4. 单元测试工具 - Mockito

4.1 Mockito 简介

在单元测试过程中主张不要依赖特定的接口与数据来源,此时就涉及到对相关数据的模拟,比如 Http 和 JDBC 的返回结果等,可以使用虚拟对象即 Mock 对象进行模拟,使得单元测试不在耦合。

Mock 过程的使用前提:

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

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

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

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

Mockito 的特点:

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

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

(3) 支持顺序验证

(4) 具备参数匹配器

4.2 Mockito 使用

maven 引入 spring-boot-starter-test 会自动将 mockito 引入到工程中。

4.2.1 使用案例

(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) 执行结果:


(5) 结果解析

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



4.2.2 相关语法

常用 API:

上述案例中用到了 mockito 的 when、any、theWhen 等语法。接下来介绍下都有哪些常用的 API:

1) mock:模拟一个需要的对象

2) when:一般配合 thenXXX 一起使用,表示当执行什么操作之后怎样。

3) any: 返回一个特定对象的缺省值,上例中标识可以填写任何 String 类型的数据。

4) theReturn: 在执行特定操作后返回指定结果。

5) spy:创造一个监控对象。

6) verify:验证特定的行为。

7) doReturn:返回结果。

8) doThrow:抛出特定异常。

9) doAnswer:做一个自定义响应。

10) times:操作执行次数。

11) atLeastOnce:操作至少要执行一次。

12) atLeast:操作至少执行指定的次数。

13) atMost:操作至多执行指定的次数。

14) atMostOnce:操作至多执行一次。

15) doNothing:不做任何的处理。

16) doReturn:返回一个结果。

17) doThrow:抛出一个指定异常。

18) doAnswer:指定一个特定操作。

19) doCallRealMethod:用于监控对象返回一个真实结果。

4.2.3 使用要点

(1) 打桩

Mockito 中有 Stub,所谓存根或者叫打桩的概念,上面案例中的 Mockito.when (bookService.orderBook (any (String.class))).thenReturn (expectBook); 就是打桩的含义,先定义好如果按照既定的方式调用了什么,结果就输出什么。然后在使用 Book book = studentService.orderBook (""); 即按照指定存根输出指定结果。

@Test
public void verifyTest() {
List mockedList = mock(List.class);
mockedList.add("one");
verify(mockedList).add("one"); // 验证通过,因为前面定义了这个桩
verify(mockedList).add("two"); // 验证失败,因为前面没有定义了这个桩
}

(2) 参数匹配

上例 StudentService 的 orderBook 方法中的 any (String.class) 即为参数匹配器,可以匹配任何此处定义的 String 类型的数据。

(3) 次数验证

@Test
public void timesTest() {
List mockedList = mock(List.class);
when(mockedList.get(anyInt())).thenReturn(1000);
System.out.println(mockedList.get(1));
System.out.println(mockedList.get(1));
System.out.println(mockedList.get(1));
System.out.println(mockedList.get(2));
// 验证通过:get(1)被调用3次
verify(mockedList, times(3)).get(1);
// 验证通过:get(1)至少被调用1次
verify(mockedList, atLeastOnce()).get(1);
// 验证通过:get(1)至少被调用3次
verify(mockedList, atLeast(3)).get(1);
}

(4) 顺序验证

@Test
public void orderBookTest1() {
String json = "{"id":12,"location":"书架A12","name":"三国演义"}";
String json1 = "{"id":21,"location":"书架A21","name":"水浒传"}";
String json2 = "{"id":22,"location":"书架A22","name":"红楼梦"}";
String json3 = "{"id":23,"location":"书架A23","name":"西游记"}";
when(bookService.orderBook("")).thenReturn(JSON.parseObject(json, Book.class));
Book book = bookService.orderBook("");
Assert.assertTrue("预定书籍有误", "三国演义".equals(book.getName()));
when(bookService.orderBook("")).thenReturn(JSON.parseObject(json1, Book.class)).
thenReturn(JSON.parseObject(json2, Book.class)).
thenReturn(JSON.parseObject(json3, Book.class));
Book book1 = bookService.orderBook("");
Book book2 = bookService.orderBook("");
Book book3 = bookService.orderBook("");
Book book4 = bookService.orderBook("");
Book book5 = bookService.orderBook("");
// 全部验证通过,按顺序最后打桩打了3次,大于3次按照最后对象输出
Assert.assertTrue("预定书籍有误", "水浒传".equals(book1.getName()));
Assert.assertTrue("预定书籍有误", "红楼梦".equals(book2.getName()));
Assert.assertTrue("预定书籍有误", "西游记".equals(book3.getName()));
Assert.assertTrue("预定书籍有误", "西游记".equals(book4.getName()));
Assert.assertTrue("预定书籍有误", "西游记".equals(book5.getName()));
}

(5) 异常验证

@Test(expected = RuntimeException.class)
public void exceptionTest() {
List mockedList = mock(List.class);
doThrow(new RuntimeException()).when(mockedList).add(1);
// 验证通过
mockedList.add(1);
}


原文链接:https://zhuanlan.zhihu.com/p/608775174?utm_id=0

标签:String,Mockito,单元测试,class,public,orderBook,测试,浅析
From: https://www.cnblogs.com/sunny3158/p/17883728.html

相关文章

  • Mockito When/Then常见用法
    MockitoWhen/Then常见用法该系列文章翻译自https://www.baeldung.com/mockito-series接下来我们将以MyList类为例进行介绍publicclassMyListextendsAbstractList<String>{@OverridepublicStringget(finalintindex){returnnull;}@Over......
  • java 单元测试之 - Spring框架提供的单元/集成测试注解
    java单元测试之-Spring框架提供的单元/集成测试注解Spring框架提供了很多注解来辅助完成单元测试和集成测试(备注:这里的集成测试指容器内部的集成测试,非系统间的集成测试),先看看Spring框架提供了哪些注解以及对应的作用。@RunWith(SpringRunner.class)/@ExtendWith(Spring......
  • Java Mockito 快速入门指南 Mock是指使用Mockito创建的模拟对象,它模拟真实对象的行为,
    JavaMockito快速入门指南Mock是指使用Mockito创建的模拟对象,它模拟真实对象的行为,用于替代真实对象的依赖项,以便进行独立的单元测试在软件开发中,单元测试对于确保代码的正确性和可靠性至关重要。Mockito是一个强大的Java测试框架,它提供了丰富的功能和方法,使得编写模拟测试变得......
  • 浅析AI智能视频监控技术在城市交通中的作用及意义
    城市交通作为整个城市的整体脉络,每天都发挥着重要作用,为了最大程度地避免城市交通堵塞、提高城市交通效率,智能视频监控系统发挥了重要作用。具体表现在以下几个方面:1、交通违规监管:TSINGSEE青犀智能视频监控系统可以通过视频监控、车辆检测等技术来监管城市交通违规行为,如道路违......
  • 单元测试平台搭建:sonarQube+sonarScanner+Jenkins+jacoco
    单元测试平台搭建及结果分析一、方案需求目标:提高单元测试覆盖率和规范代码编写规范选用工具:Sonarqube、sonarqubeScanner、Jenkins、jacoco方案:工程中引入jacoco进行代码覆盖率统计,通过sonarqubescanner扫描工程编写规范,编写单元测试代码后,结合Jenkins每次的编译部署自动......
  • vs+xunit 单元测试
    vs中nuget安装Microsoft.NET.Test.Sdk、xunit、xunit.runner.visualstudioxunit测试demopublicclassHelloTest{//xunit提供默认输出接口privatereadonlyITestOutputHelper_output;publicHelloTest(ITestOutputHelperoutput){_outp......
  • 浅析MySQL代价模型:告别盲目使用EXPLAIN,提前预知索引优化策略
    背景在MySQL中,当我们为表创建了一个或多个索引后,通常需要在索引定义完成后,根据具体的数据情况执行EXPLAIN命令,才能观察到数据库实际使用哪个索引、是否使用索引。这使得我们在添加新索引之前,无法提前预知数据库是否能使用期望的索引。更为糟糕的是,有时甚至在添加新的索引后,数......
  • @SpringBootTest 和 @RunWith 注解不能识别 单元测试第一步引入maven依赖
    @SpringBootTest和@RunWith注解不能识别单元测试第一步引入maven依赖一、背景    最近在预研 Rocketmq,在写小例子的时候,需要编写测试代码,突然间发现我的 @SpringBootTest 和 @RunWith 这两个注解不能识别,于是展开了我的问题排查过程。问题截图如下:二、问题排......
  • Mockito
    Mockito1.简介Mockito是一种JavaMock框架,主要是用来做Mock测试,它可以模拟任何Spring管理的Bean、模拟方法的返回值、模拟抛出异常等等用于快速创建和配置mock对象。通过创建外部依赖的Mock对象,然后将此Mock对象注入到测试类中,简化有外部依赖的类的测试。我们......
  • 三维模型的顶层合并构建的优势方面浅析
    三维模型的顶层合并构建的优势方面浅析 倾斜摄影超大场景的三维模型的顶层合并具有许多优势,本文将对其进行浅析。一、全面的信息获取倾斜摄影超大场景的三维模型的顶层合并能够从不同视角和角度获取全面的信息。通过倾斜摄影技术,可以在一个扫描过程中获得大范围的地理数据,并......