【软件测试】Junit单元测试

简介: 【软件测试】Junit单元测试

一、单元测试

1.单元测试是什么?

借用一下百度百科的话,


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


这里有几个关键点:


单元是人为规定的

单元测试是独立单元,要和其他部分相分离。

2.为什么需要单元测试?

这里谈谈我自己的感受,就我自己而言,是没有单元测试的习惯的,我感觉单元测试会非常耗费时间,同时我认为这些时间花费的不太值得,因为在我初学阶段,做的都是一些简单的crud项目。

但是随着我开发的项目越来越大,需求越来越复杂,我渐渐发现我做的项目质量越来越不稳定,常常会出现一些奇怪的bug。当出现bug时,我们往往要定位问题的所在。就比如前段时间我自己做的一个通用物联网平台,在录制演示视频时输入矫正公式(支持四则运算),当时在其他设备下都能正常运作,但偏偏那次出现了异常。好在整个项目都是自己做的,对于一些实现细节上也都心里有数,调试了一会后发现是算法问题,那时我才猛然想起自己写的时候明白这个算法实现不支持负数,如果要进行负数运算得变成“(0-x)”的形式,而恰巧那台设备上传的数据是负数,所以出现了问题。


好在是全栈开发,所有东西都是自己做的,如果这个项目是团队开发,我估计定位bug的所耗费的时间将会指数级增长。


正因为在集成测试等大规模测试中,定位bug所耗费的时间实在是太长了,所以我们需要单元测试来保证每个小模块的正确性。 尽管它会耗费更多的时间,但是这些时间比起后期层出不穷的bug以及解决bug所耗费的是时间,这些都是值得的。


在开发项目的过程中,很多时候都是在解决之前的bug遗留。


去.png

我在网上看到过相关的总结,写的非常好分享一下——单元测试到底是什么?应该怎么做?


单元测试对我们的产品质量是非常重要的。

单元测试是所有测试中最底层的一类测试,是第一个环节,也是最重要的一个环节,是唯一一次有保证能够代码覆盖率达到100%的测试,是整个软件测试过程的基础和前提,单元测试防止了开发的后期因bug过多而失控,单元测试的性价比是最好的。

据统计,大约有80%的错误是在软件设计阶段引入的,并且修正一个软件错误所需的费用将随着软件生命期的进展而上升。错误发现的越晚,修复它的费用就越高,而且呈指数增长的趋势。

作为编码人员,也是单元测试的主要执行者,是唯一能够做到生产出无缺陷程序这一点的人,其他任何人都无法做到这一点代码规范、优化,可测试性的代码

放心重构

自动化执行three-thousand times

二、Junit

1.什么是junit

JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中为最成功的一个。 JUnit有它自己的JUnit扩展生态圈。

目前junit已经发展到了junit5,相较于junit4有了很大的改变。JUnit5由来自三个不同子项目的几个不同模块组成。

JUnit 5=JUnit平台+JUnit Jupiter+JUnit Vintage


详见官网


注:junit基本上Java单元测试的主流,现今大多数Java项目都有junit的身影


2.Junit概念——断言

刚接触过单元测试的同学在学习junit时肯定会疑惑assert方法到底是什么意思,什么叫断言。我一开始接触时就是这样,疑惑断言是干嘛的。


其实断言其实是一些辅助函数,他们用来帮助我们确定被测试的方法是否按照预期的效果正常工作,通常,把这些辅助函数称为断言。


3.Junit的简单使用

以下演示为maven项目


①导入依赖

<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.8.1</version>
        <scope>test</scope>
    </dependency>
    <!-- ... -->
</dependencies>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
        <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
    </plugins>
</build>


②编写测试用例

这里引用官网上的例子


import static org.junit.jupiter.api.Assertions.assertEquals;
import example.util.Calculator;
import org.junit.jupiter.api.Test;
class MyFirstJUnitJupiterTests {
    private final Calculator calculator = new Calculator();
    @Test
    void addition() {
        assertEquals(2, calculator.add(1, 1));
    }
}


上面这个例子就是断言了calculator.add(1, 1)的返回值会等于2。


在idea中运行测试将会很方便,只需点击运行图标即可

q2.png


如果不是idea中,也只需加个mian函数运行即可。


如果运行断言正确,那么程序会如下:

q3.png


如果断言错误,junit会给你抛出一个AssertionFailedError异常,并告诉你出错的情况


q4.png

4.SpringBoot环境下的junit使用

当然我们在实际开发中,比如在SpringBoot环境下开发,这时很多业务代码类都是被注入到Spring容器,而类之间又有其他注入类的依赖,像之前那样创建一个测试对象显然不现实。那有什么办法能解决这个问题呢?

下面我来介绍一下junit在SpringBoot+SSM项目中的使用。


①导入依赖

在SpringBoot中它将依赖进行整合,如果我们需要测试的相关依赖,只需引入对应的测试模块即可


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

②编写测试用例

测试对象类


package com.example.demo.service;
import org.springframework.stereotype.Component;
@Component
public class Junit5Test {
    public int add(int i,int j){
        System.out.println("-----------add被执行了---------------");
        return i+j;
    }
    public int doAdd(int i,int j){
        System.out.println("------------doAdd被执行了--------------");
        //被mock的函数会先执行,且只会执行一次
        System.out.println(add(i,j));
        return add(i,j);
    }
}

测试用例


import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
//初始化一个spring的上下文,使其可以使用一些注入(junit5)。junit4会用runwith
@SpringBootTest
class Junit5TestTest {
    @Autowired
    Junit5Test junit5Test;
    //会初始化一次
    @BeforeAll
    static void init(){
        System.out.println("init");
    }
    //所有测试方法前都会执行一遍
    @BeforeEach
    void each(){
        System.out.println("each");
    }
    @Test
    void getDeviceStatistic() {
        Assertions.assertEquals(2,spyJunit5Test.doAdd(1,1));
    }
}


如果需要SpringBoot上下文环境只需在其上加个@SpringBootTest注解即可,当然在老项目中我们可能会看到@RunWith(SpringRunner.class)这种写法。前者是junit5的写法,后者是junit4的写法。


当我们需要spring容器中的测试对象时,我们只需正常注入即可。


@Autowired
Junit5Test junit5Test;

三、模拟数据——mockito框架的使用

1.mock

在实际开发进行单测时,我们测试对象很可能需要请求网络数据或者改变数据库,可是我们又不想让它去变化,这时我们可以使用mockito框架来对数据进行mock。


所谓的mock,就是指,如果我们写的代码依赖于某些对象,而这些对象又很难手动创建(即不知道如何初始化等,像HttpRequest等对象),那么就用一个虚拟的对象来测试。因为它传入的是一个class文件,所以static代码块还是会被运行,但构造函数,实例代码块都不会被执行。


2.打桩Stub

所谓打桩Stub,就是用来提供测试时所需要的测试数据,因为是mock的对象,所以可能有些方法并不能知道返回值,因此我们需要去假定返回值。可以对各种交互设置相应的回应,即对方法设置调用返回值,使用when(…).thenReturn(…)和doReturn(…).when(…)。


比如:



//You can mock concrete classes, not only interfaces
 LinkedList mockedList = mock(LinkedList.class);
 //stubbing
 when(mockedList.get(0)).thenReturn("first");
 when(mockedList.get(1)).thenThrow(new RuntimeException());

doReturn().when()是无副作用的。打桩的同时不会执行方法。

when().thenReturn()是有副作用的,其副作用是指在打桩的同时会先执行一遍方法,这时可能会造成一定的副作用。

3.@MockBean和@SpyBean

当然在SpringBoot的环境下也可以直接@SpyBean和@MockBean注解来替代@Autowired的注入对象,这样就有了一个虚拟的对象。


@MockBean

如果仅使用@MockBean,会将修饰的对象mock掉,这样Junit5Test的add()方法就不再执行具体的细节,但是MockBean会将目标对象的所有方法全部mock,所以test不能真实地被执行,也就无法测试了。


@SpyBean

而有些情况我们又需要执行真实的方法,我们只想对某些方法进行mock,这时就可以使用@SpyBean。

使用@SpyBean修饰的spyJunit5Test是一个真实对象,仅当when(spyJunit5Test.add(1,1)).thenReturn(2);时,add方法被打桩,其他的方法仍被真实调用。


以下是示例


package com.example.demo.service;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
//初始化一个spring的上下文,使其可以使用一些注入(junit5)。junit4会用runwith
@SpringBootTest
class Junit5TestTest {
//    @Autowired
//    Junit5Test junit5Test;
    //介于@Autowired和@MockBean之间的注解,当配置了when时使用mock,没有进行打桩则走正常方法
    @SpyBean
    Junit5Test spyJunit5Test;
    //完全使用mock方法,方法都不会去真正执行。当调用mockBean修饰的方法时,不会去真正执行该方法,只会返回打桩后的值,如果没有打桩时会返回默认值,比如int就返回0。
//    @MockBean
//    Junit5Test mockJunit5Test;
    //会初始化一次
    @BeforeAll
    static void init(){
        System.out.println("init");
    }
    //所有测试方法前都会执行一遍
    @BeforeEach
    void each(){
        System.out.println("each");
    }
    @Test
    void getDeviceStatistic() {
        Assertions.assertEquals(1,1);
    }
    @Test
    void testDeviceStatisticByMock() {
        //配置mock
        when(spyJunit5Test.add(1,1)).thenReturn(2);
        Assertions.assertEquals(2,spyJunit5Test.doAdd(1,1));
    }
}

最后,祝大家程序员节快乐!


相关文章
|
14天前
|
人工智能 搜索推荐 数据管理
探索软件测试中的自动化测试框架选择与优化策略
本文深入探讨了在现代软件开发流程中,如何根据项目特性、团队技能和长期维护需求,精准选择合适的自动化测试框架。
64 8
|
28天前
|
测试技术
软件测试的艺术:探索式测试的实践与思考
在软件开发的广阔海洋中,测试是确保航船稳健行驶的关键。本文将带你领略探索式测试的魅力,一种结合创造性思维和严格方法论的测试方式。我们将一起揭开探索式测试的神秘面纱,了解其核心概念、实施步骤和带来的效益。通过实际代码示例,你将学会如何将探索式测试融入日常的软件质量保证流程中,提升测试效率与质量。
|
22天前
|
测试技术 持续交付
探索软件测试中的自动化测试策略
随着软件开发周期的加速和市场需求的不断增长,传统的手动软件测试方法已难以满足现代软件开发的高效性和准确性要求。本文旨在探讨自动化测试在软件测试中的重要性、实施策略及其对提高软件质量的影响。通过分析自动化测试的优势与挑战,以及提供实用的自动化测试工具和框架选择指南,旨在帮助读者理解并应用自动化测试以提升软件开发效率和产品质量。
|
21天前
|
机器学习/深度学习 人工智能 监控
软件测试中的自动化测试策略与最佳实践##
在当今快速发展的软件行业中,自动化测试已成为确保软件质量和加速产品上市的关键工具。本文将探讨自动化测试的重要性,分析不同类型的自动化测试工具和框架,并深入讨论实施自动化测试的最佳实践。通过案例研究和数据分析,我们将揭示如何有效整合自动化测试到软件开发生命周期中,以及它如何帮助团队提高测试效率和覆盖率。 ##
31 1
|
1月前
|
机器学习/深度学习 前端开发 测试技术
探索软件测试中的自动化测试框架选择与优化策略####
本文深入探讨了在当前软件开发生命周期中,自动化测试框架的选择对于提升测试效率、保障产品质量的重要性。通过分析市场上主流的自动化测试工具,如Selenium、Appium、Jest等,结合具体项目需求,提出了一套系统化的选型与优化策略。文章首先概述了自动化测试的基本原理及其在现代软件开发中的角色变迁,随后详细对比了各主流框架的功能特点、适用场景及优缺点,最后基于实际案例,阐述了如何根据项目特性量身定制自动化测试解决方案,并给出了持续集成/持续部署(CI/CD)环境下的最佳实践建议。 --- ####
|
28天前
|
测试技术 UED 开发者
软件测试的艺术与科学:探索有效的测试策略
在软件开发的宇宙中,测试是一颗璀璨的星辰,它不仅保障着产品的质量,也指引着项目的方向。本文将带你穿梭于测试的银河系,从基础的单元测试到复杂的集成测试,再到全面的系统测试,我们将一探究竟。你会发现,每一个测试阶段都是一次对代码深度和广度的挑战,也是一次对开发者耐心和智慧的考验。准备好了吗?让我们开始这段探索之旅,看看如何通过精心设计的测试案例来确保我们的软件能够在现实世界中稳健运行。
|
19天前
|
Java 测试技术 API
探索软件测试中的自动化测试框架
本文深入探讨了自动化测试在软件开发中的重要性,并详细介绍了几种流行的自动化测试框架。通过比较它们的优缺点和适用场景,旨在为读者提供选择合适自动化测试工具的参考依据。
|
20天前
|
数据管理 测试技术 持续交付
软件测试中的自动化测试策略与最佳实践
在当今快速迭代的软件开发环境中,自动化测试已成为确保软件质量和加速产品上市的关键手段。本文旨在探讨软件测试中的自动化测试策略,包括选择合适的自动化测试工具、构建有效的自动化测试框架以及实施持续集成和持续部署(CI/CD)。通过分析自动化测试的最佳实践,本文为软件开发团队提供了一系列实用的指南,以优化测试流程、提高测试效率并减少人为错误。
49 4
|
20天前
|
监控 测试技术 定位技术
探索软件测试中的自动化测试框架选择与实施###
本文不概述传统意义上的摘要内容,而是直接以一段对话形式引入,旨在激发读者兴趣。想象一下,你是一名勇敢的探险家,面前摆满了各式各样的自动化测试工具地图,每张地图都指向未知的宝藏——高效、精准的软件测试领域。我们将一起踏上这段旅程,探讨如何根据项目特性选择合适的自动化测试框架,并分享实施过程中的关键步骤与避坑指南。 ###
30 4
|
20天前
|
测试技术 持续交付 数据安全/隐私保护
软件测试的艺术与科学:探索自动化测试框架
在软件开发的世界中,测试是确保产品质量的关键环节。本文将深入探讨自动化测试框架的重要性和实现方法,旨在为读者揭示如何通过自动化测试提升软件测试效率和准确性。我们将从测试的基本概念出发,逐步引导读者了解自动化测试框架的设计和实施过程,以及如何选择合适的工具来支持测试活动。文章不仅提供理论知识,还将分享实用的代码示例,帮助读者将理论应用于实践。无论你是测试新手还是经验丰富的开发者,这篇文章都将为你打开一扇通往更高效、更可靠软件测试的大门。
20 1