【单元测试】一文读懂java单元测试

目录

  • 1. 什么是单元测试
  • 2. 为什么要单元测试
  • 3. 单元测试框架 - JUnit
    • 3.1 JUnit 简介
    • 3.2 JUnit 内容
    • 3.3 JUnit 使用
      • 3.3.1 Controller 层单元测试
      • 3.3.2 Service 层单元测试
      • 3.3.3 Dao 层单元测试
      • 3.3.4 异常测试
      • 3.3.5 测试套件测多个类
      • 3.3.6 idea 中查看单元测试覆盖率
      • 3.3.7 JUnit 插件自动生成单测代码
      • 3.3.8 常用注解和配置

1. 什么是单元测试

单元测试是软件开发中常用的一种测试方法,用于验证代码的单个功能单元是否按照预期工作。
测试方法:

  • 白盒测试(White Box Testing):在白盒测试中,测试人员了解代码的内部结构和实现细节,编写测试用例来覆盖不同的代码路径和逻辑条件。
  • 黑盒测试(Black Box Testing):黑盒测试不考虑代码的内部实现,而是基于需求规格说明或功能规范编写测试用例,测试程序的输入和输出是否符合预期。
  • 单元测试框架:使用单元测试框架可以简化单元测试的编写和执行。常见的单元测试框架包括JUnit(Java)、NUnit(.NET)、pytest(Python)等。
  • 断言(Assertion):在单元测试中,断言用于检查预期结果和实际结果是否匹配。测试人员可以使用断言来验证程序的特定行为和结果。
  • 边界值测试(Boundary Value Testing):边界值测试通过选择测试用例中的边界条件,例如最小值、最大值、临界值等,来验证程序在边界情况下的行为。
  • 异常处理测试(Exception Handling Testing):异常处理测试用于验证程序在遇到异常情况时是否能够正确地捕获和处理异常,并保证系统的稳定性和可靠性。
  • 参数化测试(Parameterized Testing):参数化测试允许在单个测试用例中使用不同的参数进行多次测试,以增加测试覆盖率和复用性。
    这些方法都可以根据具体的需求和开发环境选择使用。单元测试的目标是尽可能地覆盖代码,确保每个单元都能按照预期工作,并提高软件的质量和可维护性。

这些方法都可以根据具体的需求和开发环境选择使用。单元测试的目标是尽可能地覆盖代码,确保每个单元都能按照预期工作,并提高软件的质量和可维护性。

2. 为什么要单元测试

(1)单元测试意义:

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

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

@PostMapping("/add")
public void addStudent(@RequestBody Student student){studentService.save(student);
}

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

public static void main(String[] args) {RestTemplate restTemplate = new RestTemplate();HttpHeaders httpHeaders = new HttpHeaders();httpHeaders.setContentType(MediaType.APPLICATION_JSON);String json = "{\"id\":4,\"name\":\"阿狸\",\"classname\":\"初三一班\",\"age\":16,\"sex\":\"女\"}";HttpEntity<String> httpEntity = new HttpEntity<>(json, httpHeaders);String url = "http://localhost:8080/student/add";ResponseEntity<Map> responseEntity = restTemplate.postForEntity(url, httpEntity, Map.class);System.out.println(responseEntity.getBody());
}

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

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

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

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

3. 单元测试框架 - JUnit

3.1 JUnit 简介

Unit 官网: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><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.1</version><scope>test</scope>
</dependency>

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

@RunWith(SpringRunner.class)
@SpringBootTest(classes = UnittestDemoApplication.class)
class StudentControllerTest {@Autowiredprivate WebApplicationContext webApplicationContext;private MockMvc mockMvc;@BeforeEachvoid setUp() {mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();}@Testvoid testAddStudent() throws Exception {// 创建一个Student对象作为请求的JSON体Student student = new Student();student.setId(6);student.setName("小乔");student.setClassname("初二三班");student.setAge(14);student.setSex("女");// 将Student对象转换为JSON字符串ObjectMapper objectMapper = new ObjectMapper();String json = objectMapper.writeValueAsString(student);// 发送POST请求mockMvc.perform(MockMvcRequestBuilders.post("/student/add").contentType(MediaType.APPLICATION_JSON_UTF8).accept(MediaType.APPLICATION_JSON_UTF8).content(json))// 断言返回的状态码为200.andExpect(MockMvcResultMatchers.status().isOk())// 断言返回的JSON中包含指定的code和message.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(200)).andExpect(MockMvcResultMatchers.jsonPath("$.message").value("保存成功")).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 {@Autowiredprivate StudentService studentService;@Testpublic void getOne(){Student stu = studentService.getById(1);Assert.assertThat(stu.getName(), CoreMatchers.is("张三"));}
}

执行结果:

在这里插入图片描述

反例:

@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentServiceTest {@Autowiredprivate StudentService studentService;@Testpublic void getOne(){Student stu = studentService.getById(1);Assert.assertThat(stu.getName(), CoreMatchers.is("李四"));}
}

执行结果:

在这里插入图片描述

3.3.3 Dao 层单元测试

可以编写如下代码对 Dao 层保存方法进行单测:

@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentDaoTest {@Autowiredprivate StudentMapper studentMapper;@Test@Rollback(value = true)@Transactionalpublic void insertOne() throws Exception {Student student = new Student();student.setId(7);student.setName("王五");student.setClassname("大一");student.setAge(20);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;int c = a/b;
}

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

反例

    @Testpublic void computeScoreTest() {studentService.computeScore();}

结果:

在这里插入图片描述

正例:

junit 5.0版本之前,在@Test上添加expected = ArithmeticException.class

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

junit 5.0版本之后,使用Assert.assertThrows

@Test
public void computeScoreTest() {Assert.assertThrows(ArithmeticException.class, () -> {studentService.computeScore(); // This line should throw ArithmeticException});
}

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

在这里插入图片描述

3.3.5 测试套件测多个类

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

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

在这里插入图片描述

运行结果:

在这里插入图片描述

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) 安装插件

在这里插入图片描述
(2)选择要生成单元测试的类,按Alt+Insert出现如下界面,选择TestMe自动生成文件

在这里插入图片描述
(3)选择需要的生成模板,可以根据自己实际引入的依赖选择,此处选择Junit4+Mockito
在这里插入图片描述
(4)生成的代码如下,可以生成一些基本的方法和注解,然后根据实际情况修改,可以节省一部分工作量。
在这里插入图片描述

3.3.8 常用注解和配置

@Mock:创建一个模拟的对象,类似于@Autowired,但不是真实的对象,是Mock对象,这个注解使用在类属性上

@InjectMocks:创建一个实例,其余用@Mock注解创建的mock将被注入到用该实例中,这个注解使用在类属性上

@RunWith:表示一个运行器,@RunWith(PowerMockRunner.class)表示指定用PowerMockRunner运行,这个注解使用在类上

@PowerMockIgnore:这个注解表示将某些类延迟到系统类加载器加载,解决一些类加载异常。(具体类加载异常实际中还未遇见,后续补充),这个注解在类和方法上使用

@PrepareForTest:这个注解告诉PowerMock为测试准备某些类,通常是那些需要字节码操作的类。这包括带有final、private、static或native方法的类,new一个对象时,需要特殊处理(见下面的whenNew),这个注解在类和方法上使用

@Test:@Test修饰的public void方法可以作为测试用例运行。Junit会构造一个新的类实例,然后调用所有加了@Test的方法,方法执行过程中的任何异常,都会被判定为测试用例执行失败。

@Before:@Before注解的方法中的代码会在@Test注解的方法中首先被执行。可以做一些公共的前置操作:加入一些申请资源的代码:申请数据库资源,申请io资源,申请网络资源,new一些公共的对象等等。

@After:@After注解的方法中的代码会在@Test注解的方法中首先被执行。可以做一些公共的后置操作,如关闭资源的操作。

注:可以查看注解上的注释,了解其大致用法。

代码地址GitHub

单元测试之Mockito见文章《【单元测试】单元测试之Mockito的使用》

觉得有用的话还请来个三连!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/765349.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Avl 平衡二叉树 概念与代码实现

概念: 二叉树存在值顺序插入导致树高度过大的问题、平衡二叉树通过旋转的方式、使二叉树处于平衡状态、子树高度差不能大于1 旋转规则: 左子树高右旋、右子树高左旋 右旋时插入节点在最右、则子树先左旋 左旋时插入节点在最左、则子树先右旋 代码实现: package com.info.data…

第28章 ansible的使用

第28章 ansible的使用 本章主要介绍在 RHEL8 中如何安装 ansible 及 ansible的基本使用。 ◆ ansible 是如何工作的 ◆ 在RHEL8 中安装ansible ◆ 编写 ansible.cfg 和清单文件 ◆ ansible 的基本用法 文章目录 第28章 ansible的使用28.1 安装ansible28.2 编写ansible.cfg和清…

Arrays.binarySearch 详解

Arrays.binarySearch 详解 Arrays类的binarySearch()方法&#xff0c;可以使用二分搜索法来搜索指定的数组&#xff0c;以获得指定对象。该方法返回要搜索元素的索引值。务必注意&#xff1a;数组必须经过排序才可以使用此方法&#xff0c;否则返回下标显示不准。binarySearch…

HDFS集群环境配置

环境如下三台服务器&#xff1a; 192.168.32.101 node1192.168.32.102 node2192.168.32.103 node3 一、Hadoop安装包下载&#xff0c;点此官网下载 二、Hadoop HDFS的角色包含&#xff1a; NameNode&#xff0c;主节点管理者DataNode&#xff0c;从节点工作者SecondaryNameN…

掌握Go语言:Go语言结构体进阶,探索高级特性与实用技巧(23)

Go语言中的结构体&#xff08;Struct&#xff09;除了基本的定义和使用外&#xff0c;还有一些高级用法&#xff0c;可以让我们更灵活地使用结构体。下面详细解释一些高级用法&#xff1a; 结构体嵌套 结构体可以嵌套在其他结构体中&#xff0c;形成更复杂的数据结构。这种嵌…

Linux 中使用ISO文件 作为yum源

Linux 中使用ISO文件 作为yum源 在Linux中&#xff0c;可以将ISO文件挂载为本地YUM源。以下是如何操作的步骤和示例代码&#xff1a; 挂载ISO文件到某个目录&#xff08;例如/mnt/iso&#xff09;&#xff1a; mkdir /mnt/cdrom mount -o loop /path/to/your.iso /mnt/cdrom…

【科普向】什么是数据湖架构

数据湖架构是一种用于存储和管理大规模数据的设计模式。它的核心思想是将各种类型和格式的数据以原始形式存储在一个集中的存储系统中&#xff0c;而不需要预先定义数据结构或模式。以下是我对数据湖架构的理解和相关经验&#xff1a; 1. 构建数据湖&#xff1a;构建数据湖需要…

React Native: could not connect to development server

问题&#xff1a; 运行模拟器错误&#xff1a;无法连接到开发服务器 原因分析&#xff1a; 1、确认模拟器连接状态&#xff0c;是连接成功的 查看进程的端口占用&#xff0c;也没问题 lsof -i tcp:8081 kill pid2、检查包服务器是否运行正常 连接真机进行调试发现真机是正常…

基于springboot+vue+Mysql的“智慧食堂”设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

Python 如何优雅编写多进程读取文件代码

文章目录 1. 背景2. multiprocessing库介绍2.1 创建进程2.2 进程池2.3 进程间通信2.4 进程同步 3. 多进程读取文件代码 1. 背景 在读取文件时&#xff0c;希望能够利用 Python 的多进程并行读取多个文件。有时&#xff0c;我们是基于类去编写代码的&#xff0c;那么对于类的方…

Basic RNN

文章目录 回顾RNNRNN CellRNNCell的使用RNN的使用 RNN例子使用RNN Cell实现使用RNN实现 嵌入层 Embedding独热向量的缺点Embedding LSTMGRU(门控循环单元)练习 回顾 DNN&#xff08;全连接&#xff09;&#xff1a;和CNN相比&#xff0c;拥有巨大的参数量&#xff0c;CNN权重共…

游泳耳机哪个牌子好?强烈推荐这4大高性能款式!

在如今的科技时代&#xff0c;游泳耳机已经成为了许多游泳爱好者和运动员的必备装备。一款好的游泳耳机不仅可以让你在水中享受到美妙的音乐&#xff0c;还可以为你提供更好的训练体验。 &#xff08;下图是我测试过的一部分游泳耳机&#xff1a;&#xff09; 但在市场上众多的…

基于FPGA实现的UDP协议栈设计_汇总

基于FPGA实现的千兆以太网UDP协议栈设计&#xff08;汇总篇&#xff09; 1. MAC设计 2. IP层设计 3. ARP层设计 4. UDP层设计 5. ICMP层设计 6. 仲裁器设计 8. RGMII接口设计 9. 跨时钟域设计

公募基金公开市场数据基础分析实例

公募基金公开市场数据基础分析 公募基金公开市场数据基础分析示例代码1.1 公开市场数据表结构 字段 字段类型 含义 SecurityID SYMBOL 基金代码 FullName STRING 基金全称 Name STRING 基金简称 Management SYMBOL 基金公司 Type SYMBOL 基金类型 Custodian SYMBOL 托管人 Issu…

AI大模型在医疗领域的应用案例:自然语言处理与医疗文本分析

随着人工智能技术的快速发展&#xff0c;AI大模型在自然语言处理、图像识别、语音识别等领域的应用越来越广泛。在医疗领域&#xff0c;AI大模型的应用正在深刻改变着医疗实践&#xff0c;为患者和医生带来前所未有的便利。近期AI医疗的概念也比较火热&#xff0c;本文将聚焦于…

编程题:相同数字的积木游戏(Java)

题目描述 小华和小薇一起通过玩积木游戏学习数学。 他们有很多积木&#xff0c;每个积木块上都有一个数字&#xff0c;积木块上的数字可能相同。 小华随机拿一些积木挨着排成一排&#xff0c;请小薇找到这排积木中数字相同目所处位置最远的2块积木块&#xff0c;计算他们的…

css的border详解

CSS的border属性是一个简写属性&#xff0c;用于设置以下四个边框属性&#xff1a; border-width&#xff1a;定义边框的宽度。可以使用具体的像素值&#xff0c;或者使用预定义的关键字如thin、medium和thick。border-width不支持百分比值。默认情况下&#xff0c;边框的宽度是…

在线文本列表差集计算器

具体请前往&#xff1a;在线文本差集计算工具

system Verilog:clocking中定义信号为input和output的区别

在SystemVerilog中&#xff0c;clocking块用于定义时钟块&#xff0c;这通常用于描述时钟边缘和同步的输入/输出行为&#xff0c;特别是在测试平台和硬件接口描述中。 在下述两个代码示例中&#xff0c;主要区别在于a被定义为一个input还是output。 当a被定义为input时&#x…

云计算 3月22号 (mysql的主从复制)

一、MySQL-Replication&#xff08;主从复制&#xff09; 1.1、MySQL Replication 主从复制&#xff08;也称 AB 复制&#xff09;允许将来自一个MySQL数据库服务器&#xff08;主服务器&#xff09;的数据复制到一个或多个MySQL数据库服务器&#xff08;从服务器&#xff09;…