【软件测试】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));
    }
}

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


相关文章
|
1月前
|
IDE Java 测试技术
Junit 单元测试
JUnit是Java常用的单元测试框架,简化了测试用例的编写和执行。其特点包括简单注解、自动化测试、可扩展性、灵活性及与IDE的集成。使用方法涉及创建测试类、利用注解如@Test、@BeforeEach等管理测试生命周期,以及使用各种断言方法验证结果。此外,JUnit支持参数化测试以覆盖更多输入组合,并能与Maven、Gradle等构建工具集成,提升测试效率和项目管理。
38 1
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
提升软件测试效率与质量:AI驱动的自动化测试策略
【2月更文挑战第19天】 在快速迭代的软件发展环境中,传统的手动测试方法已无法满足高效率和高质量的要求。本文探讨了人工智能(AI)技术如何革新现有的软件测试流程,通过引入AI驱动的自动化测试策略,旨在提高测试覆盖率,减少人为错误,优化资源分配,并缩短产品上市时间。我们将分析AI在识别潜在缺陷、生成测试用例、执行测试以及结果分析中的应用,并讨论实施这些策略时可能遇到的挑战和限制。
144 3
|
17天前
|
Java 测试技术
SpringBoot整合单元测试&&关于SpringBoot单元测试找不到Mapper和Service报java.lang.NullPointerException的错误
SpringBoot整合单元测试&&关于SpringBoot单元测试找不到Mapper和Service报java.lang.NullPointerException的错误
21 0
|
16天前
|
Java 测试技术 程序员
junit单元测试
junit单元测试
|
13天前
|
缓存 自动驾驶 测试技术
如何进行有效的Apollo测试:单元测试和集成测试指南
如何进行有效的Apollo测试:单元测试和集成测试指南
41 13
|
20天前
|
jenkins 测试技术 持续交付
软件测试|docker搭建Jenkins+Python+allure自动化测试环境
通过以上步骤,你可以在Docker中搭建起Jenkins自动化测试环境,实现Python测试的自动化执行和Allure报告生成。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
39 6
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
提升软件测试效率:AI驱动的自动化测试策略
【2月更文挑战第30天】随着人工智能(AI)在软件开发周期中的日益普及,其在提高软件测试效率方面的潜力正受到越来越多的关注。本文探讨了如何通过集成AI技术来优化自动化测试流程,从而减少重复工作、提高错误检测率和加快反馈速度。我们将分析当前AI在自动化测试中的应用,并提出一系列策略以利用AI改进测试案例生成、执行和维护过程。
83 0
|
1月前
|
XML Java 测试技术
TestNG 与 JUnit 测试框架:哪个更好?
【2月更文挑战第16天】
46 1
TestNG 与 JUnit 测试框架:哪个更好?
|
2月前
|
关系型数据库 MySQL 测试技术
【软件测试】 初识软件测试
【软件测试】 初识软件测试
|
2月前
|
人工智能 前端开发 Java
软件测试/人工智能|熟练使用web控件定位技巧,提升测试工作效率!
软件测试/人工智能|熟练使用web控件定位技巧,提升测试工作效率!
197 1