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

news2024/9/26 3:30:38

目录

  • 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 {

    @Autowired
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

    @BeforeEach
    void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    void 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 {
 
	@Autowired
    private StudentService studentService;
 
    @Test
    public void getOne(){
        Student stu = studentService.getById(1);
        Assert.assertThat(stu.getName(), CoreMatchers.is("张三"));
    }
}

执行结果:

在这里插入图片描述

反例:

@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentServiceTest {
 
	@Autowired
    private StudentService studentService;
 
    @Test
    public 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 {
 
	@Autowired
    private StudentMapper studentMapper;
 
    @Test
    @Rollback(value = true)
    @Transactional
    public 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 的测试类中定义单元测试方法:

反例

    @Test
    public 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.coloradmin.cn/o/1539044.html

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

相关文章

Excel使用VLOOKUP函数

VLOOKUP(lookup_value,table_array,col_index_num,range_lookup) 释义&#xff1a; lookup_value&#xff1a;要查找的值&#xff0c;包括数字&#xff0c;文本等 table_array&#xff1a;要查找的值以及预期返回的内容所在的区域 col_index_num&#xff1a;查找的区域的列…

安装mysql8.0.36遇到的问题没有developer default 选项问题

安装mysql8.0.36的话没有developer default选项&#xff0c;直接选择customer就好了&#xff0c;点击next之后通过点击左边Available Products里面的号和中间一列的右箭头添加要安装的产品&#xff0c;最后会剩下6个 安装完成后默认是启动了&#xff0c;并且在电脑注册表注册了…

机器学习——决策树剪枝算法

机器学习——决策树剪枝算法 决策树是一种常用的机器学习模型&#xff0c;它能够根据数据特征的不同进行分类或回归。在决策树的构建过程中&#xff0c;剪枝算法是为了防止过拟合&#xff0c;提高模型的泛化能力而提出的重要技术。本篇博客将介绍剪枝处理的概念、预剪枝和后剪…

《优化接口设计的思路》系列:第九篇—用好缓存,让你的接口速度飞起来

一、前言 大家好&#xff01;我是sum墨&#xff0c;一个一线的底层码农&#xff0c;平时喜欢研究和思考一些技术相关的问题并整理成文&#xff0c;限于本人水平&#xff0c;如果文章和代码有表述不当之处&#xff0c;还请不吝赐教。 作为一名从业已达六年的老码农&#xff0c…

vue2 自定义 v-model (model选项的使用)

效果预览 model 选项的语法 每个组件上只能有一个 v-model。v-model 默认会占用名为 value 的 prop 和名为 input 的事件&#xff0c;即 model 选项的默认值为 model: {prop: "value",event: "input",},通过修改 model 选项&#xff0c;即可自定义v-model …

35 跨域相关问题, 以及常见的解决方式

前言 跨域相关 这是一个 经常会碰到的问题 然后 常见的解决方式 也大概就是几种, 各有各的问题 这里仅仅是 从理论上 来探讨这个问题 主流的解决方式 是通过代理, 将不同域 合并到同一个域 测试用例 测试用例如下, 这里仅仅是一个简单的数据展示 获取对方 “/config.jso…

【c++入门】引用,内联函数,auto

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 朋友们大家好&#xff0c;本节我们来到c中一个重要的部分&#xff1a;引用 目录 1.引用的基本概念与用法1.1引用特性1.2使用场景1.3传值、传引用效率比较1.4引用做返回值1.5引用和指针的对…

Kubernetes(k8s)集群健康检查常用的五种指标

文章目录 1、节点健康指标2、Pod健康指标3、服务健康指标4、网络健康指标5、存储健康指标 1、节点健康指标 节点状态&#xff1a;检查节点是否处于Ready状态&#xff0c;以及是否存在任何异常状态。 资源利用率&#xff1a;监控节点的CPU、内存、磁盘等资源的使用情况&#xf…

SpringCloud从入门到精通速成(二)

文章目录 1.Nacos配置管理1.1.统一配置管理1.1.1.在nacos中添加配置文件1.1.2.从微服务拉取配置 1.2.配置热更新1.2.1.方式一1.2.2.方式二 1.3.配置共享1&#xff09;添加一个环境共享配置2&#xff09;在user-service中读取共享配置3&#xff09;运行两个UserApplication&…

c语言食堂就餐排队问题290行

定制魏&#xff1a;QTWZPW&#xff0c;获取更多源码等 目录 题目 数据结构 函数设计 结构设计 总结 效果截图 ​ 主函数代码 题目 设计一个程序来模拟食堂就餐排队问题&#xff0c;通过输入学生人数和面包数量&#xff0c;计算有多少学生能够吃到午餐。 数据结构 该…

原神x星穹铁道文本转原神语音源码

《原神》x《星穹铁道》文本转原神语音源码介绍文案 探索未知的奇幻世界&#xff0c;与心仪的角色共舞冒险之旅——《原神》与《星穹铁道》的梦幻联动&#xff0c;为你带来前所未有的游戏体验&#xff01;而此刻&#xff0c;我们将为你揭秘一项革命性的创新&#xff1a;文本转原…

T470 双电池机制

ThinkPad系列电脑牛黑科技双电池管理体系技术,你知道吗&#xff1f; - 北京正方康特联想电脑代理商 上文的地址 在放电情况下&#xff1a;优先让外置电池放电&#xff0c;当放到一定电量后开始让内置电池放电。 在充电情况下&#xff1a;优先给内置电池充电&#xff0c;当充…

数据结构从入门到精通——希尔排序

希尔排序 前言一、希尔排序( 缩小增量排序 )二、希尔排序的特性总结三、希尔排序动画演示四、希尔排序具体代码实现test.c 前言 希尔排序是一种基于插入排序的算法&#xff0c;通过比较相距一定间隔的元素来工作&#xff0c;各趟比较所用的距离随着算法的进行而减小&#xff0…

c++核心学习5

4.6继承 有些类与类之间存在特殊的关系&#xff0c;例如下图中&#xff1a; 我们发现&#xff0c;定义这些类时&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性。这个时候我们就可以考虑利用继承的技术&#xff0c;减少重复代码 4.6.1继承的基本语法…

学点儿Java_Day9_字符串操作

1 实现trim方法 实现简单的trim方法&#xff0c;实现传入一个字符串&#xff0c;返回忽略前导空格和尾部空格。 public String myTrim(String str) {if (str null || str.isEmpty()) {//"".equals(str)return null;}char[] chars str.toCharArray();int start 0…

GD32串口通信PB6,PB7

我发现GD32很多接口都需要冲映射&#xff0c;刚开始还是不习惯&#xff0c;还要打开要选打开AFIO时钟。算了&#xff0c;直接看代码&#xff1a; 1,usart.c //#include "usart.h"//void USART_GPIO_init(void) //{ // //初始化引脚 // rcu_periph_clock_enable(RCU…

Qt打开已有工程方法

在Qt中&#xff0c;对于一个已有工程如何进行打开&#xff1f; 1、首先打开Qt Creator 2、点击文件->打开文件或项目&#xff0c;找到对应文件夹下的.pro文件并打开 3、点击配置工程 这样就打开对应的Qt项目了&#xff0c;点击运行即可看到对应的效果 Qt开发涉及界面修饰…

网络工程师笔记15(OSPF协议-2)

OSPF协议 OSPF是典型的链路状态路由协议&#xff0c;是目前业内使用非常广泛的 IGP 协议之一。 Router-ID(Router ldentifier&#xff0c;路由器标识符)&#xff0c;用于在一个 OSPF 域中唯一地标识一台路由器。Router-ID 的设定可以通过手工配置的方式&#xff0c;或使用系统自…

宏集PLC如何应用于建筑的3D打印?

案例概况 客户&#xff1a;Rebuild 合作伙伴&#xff1a;ASTOR 应用&#xff1a;用于建筑的大尺寸3D打印 应用产品&#xff1a;3D混凝土打印机 一、应用背景 自从20世纪80年代以来&#xff0c;增材制造技术&#xff08;即3D打印&#xff09;不断发展。大部分3D打印技术应…

day11【网络编程】-综合案例

day11【网络编程】 第三章 综合案例 3.1 文件上传案例 文件上传分析图解 【客户端】输入流&#xff0c;从硬盘读取文件数据到程序中。【客户端】输出流&#xff0c;写出文件数据到服务端。【服务端】输入流&#xff0c;读取文件数据到服务端程序。【服务端】输出流&#xf…