单元测试 订阅
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。在一种传统的结构化编程语言中,比如C,要进行测试的单元一般是函数或子过程。在像C++这样的面向对象的语言中, 要进行测试的基本单元是类。对Ada语言来说,开发人员可以选择是在独立的过程和函数,还是在Ada包的级别上进行单元测试。单元测试的原则同样被扩展到第四代语言(4GL)的开发中,在这里基本单元被典型地划分为一个菜单或显示界面。经常与单元测试联系起来的另外一些开发活动包括代码走读(Code review),静态分析(Static analysis)和动态分析(Dynamic analysis)。静态分析就是对软件的源代码进行研读,查找错误或收集一些度量数据,并不需要对代码进行编译和执行。动态分析就是通过观察软件运行时的动作,来提供执行跟踪,时间分析,以及测试覆盖度方面的信息。 展开全文
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。在一种传统的结构化编程语言中,比如C,要进行测试的单元一般是函数或子过程。在像C++这样的面向对象的语言中, 要进行测试的基本单元是类。对Ada语言来说,开发人员可以选择是在独立的过程和函数,还是在Ada包的级别上进行单元测试。单元测试的原则同样被扩展到第四代语言(4GL)的开发中,在这里基本单元被典型地划分为一个菜单或显示界面。经常与单元测试联系起来的另外一些开发活动包括代码走读(Code review),静态分析(Static analysis)和动态分析(Dynamic analysis)。静态分析就是对软件的源代码进行研读,查找错误或收集一些度量数据,并不需要对代码进行编译和执行。动态分析就是通过观察软件运行时的动作,来提供执行跟踪,时间分析,以及测试覆盖度方面的信息。
信息
性    质
概念
外文名
unit testing
释    义
对最小可测试单元进行检查和验证
中文名
单元测试
单元测试详解
单元测试(模块测试)是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list 中去,然后确认该值出现在list 的尾部。或者,你可能会从字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。单元测试是由程序员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。工厂在组装一台电视机之前,会对每个元件都进行测试,这,就是单元测试。其实我们每天都在做单元测试。你写了一个函数,除了极简单的外,总是要执行一下,看看功能是否正常,有时还要想办法输出些数据,如弹出信息窗口什么的,这,也是单元测试,把这种单元测试称为临时单元测试。只进行了临时单元测试的软件,针对代码的测试很不完整,代码覆盖率要超过70%都很困难,未覆盖的代码可能遗留大量的细小的错误,这些错误还会互相影响,当BUG暴露出来的时候难于调试,大幅度提高后期测试和维护成本,也降低了开发商的竞争力。可以说,进行充分的单元测试,是提高软件质量,降低开发成本的必由之路。对于程序员来说,如果养成了对自己写的代码进行单元测试的习惯,不但可以写出高质量的代码,而且还能提高编程水平。要进行充分的单元测试,应专门编写测试代码,并与产品代码隔离。我认为,比较简单的办法是为产品工程建立对应的测试工程,为每个类建立对应的测试类,为每个函数(很简单的除外)建立测试函数。首先就几个概念谈谈我的看法。一般认为,在结构化程序时代,单元测试所说的单元是指函数,在当今的面向对象时代,单元测试所说的单元是指类。以我的实践来看,以类作为测试单位,复杂度高,可操作性较差,因此仍然主张以函数作为单元测试的测试单位,但可以用一个测试类来组织某个类的所有测试函数。单元测试不应过分强调面向对象,因为局部代码依然是结构化的。单元测试的工作量较大,简单实用高效才是硬道理。有一种看法是,只测试类的接口(公有函数),不测试其他函数,从面向对象角度来看,确实有其道理,但是,测试的目的是找错并最终排错,因此,只要是包含错误的可能性较大的函数都要测试,跟函数是否私有没有关系。对于C++来说,可以用一种简单的方法区隔需测试的函数:简单的函数如数据读写函数的实现在头文件中编写(inline函数),所有在源文件编写实现的函数都要进行测试(构造函数和析构函数除外)。
收起全文
精华内容
下载资源
问答
  • 单元测试
    千次阅读
    2022-04-12 15:52:46

    一、什么是单元测试?

    基本概念

    **单元测试其实就是对模块、类、函数实现的功能执行检测,看看是否满足预期,是否达到功能要求,它是一次检查检验的过程。**如果某个模块或者函数满足预期,则表示测试通过,否则表示失败,比如工厂在组装一台电视机之前,会对每个元件都进行测试看是否合格,这就是单元测试,是不是很好理解。

    为什么编写单元测试

    单元测试是软件工程中降低开发成本提高软件质量常用方式之一,单元测试是一项由开发人员或者测试人员来对程序模块的正确性进行检验测试的工作,用于检查被测试代码的功能是否正确,养成单元测试的习惯,不但可以提高代码的质量,还可以提升自己的编程和技巧

    降低开发成本

    单元测试可以快速的提供反馈,将问题在开发阶段就暴露出来。这样就可以减少向下游传递的问题,比如说在系统集成阶段出现了问题,这不就很麻烦吗?因为模块、组件太多,会给你无从下手的感觉。所以做好单元测试无疑是降低了软件的开发成本。

    边界检测提高代码质量

    边界值检测方法是单元测试中常用的方法之一,举个例子,比如 1<x<10,那么边界值就是 0、2、9、11,因为在实际的使用中,我们不容易发现边界问题,比如注册用户名一般使用字母数字下划线等,那么如果使用特殊的符号又会怎么样呢?这就需要我们去检验。通过边界值检测的方法就能很好的解决这个问题,从而提高代码的质量。

    提高开发人员职业素养

    由于互联网的发展,促使了软件行业的发展,企业在遵守行业标椎的同时也制定相关的企业标准使软件开发集成上线更加精细化,形成了一套完成的流程。对于部分开发者来说一旦编码完成,他们总是会迫切希望进行软件的集成工作,这样就能够看到系统的启动了。这在外表上看来确实是明显的进步,但实际而言只是为了进度而进步,严重忽略了单元测试的重要性,若出现问题只会浪费更多的时间成本。

    二、有哪些单元测试框架?

    Android

    官方文档在 Android Studio 中测试 | Android 开发者 | Android Developers

    android中的单元测试基于JUnit4,可分为instrumented测试和本地测试。

    1.module-name/src/androidTest/java/.

    该目录下的测试代码需要运行在android设备或模拟器下面,因此可以使用android系统的API,速度较慢。

    **优点:**直观,运行在真机上

    **缺点:**运行速度慢,需要编译安装app

    **代表框架:**UIAutomator,Robotium,Espresso,Macaca,Appium等

    2.module-name/src/test/java/.

    该目录下的代码运行在本地JVM上,其优点是速度快,不需要设备或模拟器的支持,但是无法直接运行含有android系统API引用的测试代码。

    **优点:**速度快,使用简单,方便

    **缺点:**不够直观,比如有硬件相关(比如wifi,Bluetooth等),兼容性问题无法测试出来。

    **代表框架:**Mockito,EasyMockito,Jmockit Powermock,Robolectric等

    JUnit

    JUnit是Java最基础的测试框架,主要的作用就是断言。

    Android单元测试(一):JUnit框架的使用_android单元测试

    一个测试类单元测试的执行顺序为: @BeforeClass –> @Before –> @Test –> @After –> @AfterClass

    JUnit是单元测试的根基。

    Mock

    如果被测的业务依赖比较复杂的上下文,就可以使用Mock来模拟被测代码依赖的对象来保证单元测试的进行。

    所谓的 Mock 就是创建一个类的虚假的对象,在测试环境中,用来替换掉真实的对象,主要提供两大功能:

    • 验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
    • 指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作

    要使用 Mock,一般需要用到 Mock 框架

    【腾讯TMQ】【测试左移专栏】用Powermock和Mockito来做安卓单元测试

    使用详解:https://blog.csdn.net/cpcpcp123/article/details/121655192

    Robolectric

    对于Android app来说,写起单元测试来瞻前顾后,一方面单元测试需要运行在模拟器上或者真机上,麻烦而且缓慢,另一方面,一些依赖Android SDK的对象(如Activity,TextView等)的测试非常头疼,Robolectric可以解决此类问题,它的设计思路便是通过实现一套JVM能运行的Android代码,从而做到脱离Android环境进行测试。

    使用详解:

    • http://www.javashuo.com/article/p-czaryuia-n.html
    • https://juejin.cn/post/6844903428762812423

    案例:https://github.com/geniusmart/LoveUT

    Espresso

    谷歌官方提供的UI自动化测试框架,需要运行在手机/模拟器上,类似于Appium

    主要特点

    优点

    • Espresso的测试执行速度相对较快。
    • Espresso是一个高度稳定的Android测试自动化框架,因为它在适当的时间运行UI测试以防止系统负载。
    • 如果你使用Android Studio开发Android app,Espresso可能是你的最佳选择,因为它易于集成。
    • Espresso基于JUnit——一种非常流行的测试语言。因此,应用测试人员在选择此框架时可以快速选择API。

    缺点

    • **粒度粗。**Espresso本质上就是一种UI自动化测试方案,很难去验证函数级别的逻辑,如果仅仅是想验证某个功能是否正常的话,又受限于网络状况、设备条件甚至用户账户等等因素,测试结果不可控。
    • **逻辑复杂。**一般页面UI元素庞大且复杂,不可能每个View的交互逻辑都去写测试代码验证,只能选择性验证一些关键交互。
    • **运行速度慢。**用Espresso写测试代码,必须跑在emulator或者是device上面。运行测试用例就变成了一个漫长的过程,因为要打包、上传到机器、然后再一个一个地运行UI界面,这样做的好处是手机上的表现很直观,但是调试和运行速度是真的慢,效率和便捷性上肯定是不如人工测试。

    结论:Espresso用例的编写就像是在做业务代码的逆向实现,在实际工作中还不如直接运行项目代码进行人工自测,所以个人感觉Espresso是一个强大的UI自动化测试工具,而非单元测试的解决方案。

    Selendroid

    使用Selendroid测试原生或混合Android app和移动网络的自动化。

    主要特点

    • Selendroid可与在线Android模拟器、真实设备和Selenium Grid一起使用;
    • Selendroid使用在应用测试人员中非常流行的Selenium,他们可以立即开始使用Android测试自动化框架,而无需花时间学习;
    • Selendroid带有高级用户交互支持API,可加快测试速度。这些可能包括人机交互,例如滑动和拖放;
    • Android测试框架支持JSON Wire协议;
    • Selendroid带有一个高级检查器,可以快速检测UI元素。

    案例

    • https://gitcode.net/fepengwang/AndroidUT

    结论

    • JUnit是基础。
    • 需要依赖Android平台,android studio自带AndroidJunitRunner
    • Espresso需要跑在真机上,用于依赖Android平台的功能测试而非单元测试。
    • Roboelctric用于用于依赖Android平台的开源框架
    • 需要mock,可以使用MockitoPowerMockito
    • 多个测试框架可相互结合使用。如:https://www.cnblogs.com/aademeng/articles/6881574.html

    IOS

    XCTest

    当你必须测试iOS移动app时,XCTest被认为是主要和最受欢迎的iOS测试自动化框架。iOS 测试框架使用实例方法,为iOS app开发人员创造了一个友好的环境。XCTest使用Objective-C和Swift编程语言进行测试,兼容XCode 5.0+。

    主要特点

    • XCTest是一个强大的iOS测试框架,可用于单元测试、性能测试和UI测试;
    • 用于编写测试用例的语言与开发类似,因此开发人员和app测试人员可以立即开始测试;
    • XCTest可以很好地控制持续集成工具;
    • XCTest还允许用户界面记录和增强。

    EarlGrey

    “EarlGrey是一个原生的iOS UI自动化测试框架,可让你编写清晰、简洁的测试。”

    Earlgrey是由Google开发的开源iOS测试框架,目前在其第二个主要版本上运行。谷歌开发Earlgrey的想法是测试他们的原生iOS app,包括YouTube、Gmail等。因此,Earlgrey 1.0是一个白盒移动app测试框架,而Earlgrey 2可以执行白盒和黑盒测试。

    主要特点

    • Earlgrey凭借其出色的同步能力得以生存。移动应用测试框架可以自动与UI、网络请求等同步;
    • 此外,Earlgrey给出了手动实现计时的范围;
    • Earlgrey利用直接从XCode运行测试的功能,可以与XCode一起使用。

    自动化测试框架

    Calabash

    Calabash是一个开源移动应用测试框架,可免费用于测试在Android或iOS上运行的移动应用。Calabash使用Ruby语言来执行测试,但如果你正在构建自定义步骤,则无需任何编码知识即可使用Cucumber轻松执行此操作。但是,自定义步骤需要对其文档中提供的Ruby API进行扩展。

    主要特点

    • Calabash支持真实设备和模拟器来执行自动化测试用例;
    • 该框架因作为最稳定的移动app测试框架之一而受到高度赞赏;
    • Calabash可以很容易地与Jenkins等持续集成工具集成;
    • 该框架使用行为驱动的开发,并且不要求应用测试人员提供编码语言。

    Appium

    如果你在测试领域,很有可能你已经听说过Appium。Appium在过去的8.5年中一直处于测试自动化领域,此后它的受欢迎程度一直在增加。

    这个移动app测试框架是高度通用的。因此,它是应用测试人员最推荐的框架之一。

    主要特点

    • Appium支持几乎所有编程语言和所有框架。因此,应用测试人员无需学习新的框架或语言,就可以立即开始测试;
    • Appium移动应用测试框架通过对多个平台使用相同的API来促进代码的可重用性;
    • 该框架拥有一个非常庞大且活跃的社区,可以立即帮助你解决所有问题。

    参考资料

    Android单元测试框架选择

    Android单元测试框架介绍 – 调研篇_cmyperson的博客-CSDN博客_android单元测试框架

    Android单元测试框架-设计思路_一杯苦芥的博客-CSDN博客_安卓单元测试框架

    https://baijiahao.baidu.com/s?id=1722345139881400561&wfr=spider&for=pc

    更多相关内容
  • 单元测试

    千次阅读 2020-08-11 14:56:41
    单元测试是开发自己编写的针对代码某个功能模块验证其行为的测试单元模块;单元测试贯穿在开发的整个过程,并伴随着新功能模块的产生而进行;单元测试并不会花费更多的时间,与之相反,在提高代码效率、减少bug数量...

    概述

    单元测试是开发自己编写的针对代码某个功能模块验证其行为的测试单元模块;单元测试贯穿在开发的整个过程,并伴随着新功能模块的产生而进行;单元测试并不会花费更多的时间,与之相反,在提高代码效率、减少bug数量、有序开展开发工作上,单元测试发挥着很大的作用。

    场景示例

    一个开发者因为最上层的代码运行没有任何输出,采用单步调试来跟踪并发现了一个bug,在他纠正了这个bug的同时又找到了好几个其它的bug;如此几次过后,bug还是存在;而程序输出这边,仍然没有结果;于是,这个开发者已经完全搞不清为什么会这样,并认为这种没有输出的行为是毫无道理的。

    如果针对上面这个场景引入单元测试,情况会是这样的:
    在开发过程中,每写一个函数就添加一个简单的测试来判断函数功能和所期望的是否一致;在未对刚写的函数做出确认之前,开发者并不会接着写新代码;也就是每写一个函数,必然是在验证其功能可用的情况下才引入新的功能的开发;而最后结果则是,因为有单元测试保障每一个新增函数的功能都是可用的,因而最后的最上层程序也是有输出的,而不会出现之前第一种场景里那种完全无厘头的情况。

    误区纠正

    编写单元测试太费时间——相比在项目结束时才进行的测试工作会花费更多的时间,用在单元测试上的时间是要少得多的;当然,前提是开发者必须要对所要测试的单元要实现什么样的功能,期望输出是怎样的要十分了解才行;

    运行测试的时间太长——实际上,大多数测试的执行都是非常快的,因此通常几秒之内可以运行成千上万个测试;有时某些测试会花费很长的时间,但这样的测试并不一定要每次都运行;要将这些耗时的测试和其他测试分开,耗时测试一天运行一次或者几天运行一次,运行较快的测试则可以经常运行;

    测试代码并不是开发的工作——如果一个开发者把随手编写的一块没有把握的代码随便地扔给测试组,那么实际上这个开发者并没有完成他的工作;实际上,期望别人来清理自己的代码是很不好的做法;

    这些代码都能够编译通过——有一种很普遍的误解是,一个成功的编译就是成功的标记;实际上是,任何编译器和解释器都只能验证语法的正确性,而并不能验证行为的正确性

    测试环境搭建

    maven

    现阶段,大部分工程基于maven搭建

    maven + junit

    junit

    在java开发中,单元测试基于junit框架

    搭建

    在maven工程的pom.xml文件中添加:

    <dependency>
    
            <groupId>junit</groupId>
    
            <artifactId>junit</artifactId>
    
            <version>4.12</version>
    
    </dependency>
    

    maven+PowerMock

    Powermock

    单元测试的目标是一次只验证一个方法,但是倘若遇到这样的情况:某个方法依赖于系统的其他部分(如网络、数据库,甚至是servlet引擎)。在这种情况下,倘若不小心,最后会发现几乎初始化了系统的每个组件就只是为了给某一个测试创造足够的运行环境让它可以运行起来。如果这么做的话,相当于把单元测试做成了集成测试,这也违背了单元测试是要让开发更为高效的初衷。

    所以,当所测试的模块需要依赖于外部的环境或者是其它模块的时候,我们就应该考虑去Mock(模拟)一下那些所依赖的模块了。至于Mock的方法,可以自己去写一个所依赖的模块,当然也可以采用第三方的框架——即将整个外界环境整合成一个模拟框架模型(MFM, Mock Framework Model)。

    在这里插入图片描述
    PowerMock具有更为强大的功能,它对现有Mock框架进行了封装,能够弥补EasyMock等框架不能Mock私有方法、静态方法、单例等的缺点,达到其他Mock框架所不能达到的效果。
    PowerMock封装的单元测试框架如下图所示:
    在这里插入图片描述

    搭建

    在pom文件中添加依赖(dependency):

    <dependency>
    
            <groupId>org.powermock</groupId>
    
            <artifactId>powermock-module-junit4</artifactId>
    
            <version>1.6.5</version>
    
    </dependency>
    
    <dependency>
    
            <groupId>org.powermock</groupId>
    
            <artifactId>powermock-api-mockito</artifactId>
    
            <version>1.6.5</version>
    
    </dependency>
    

    注:需要两个插件powermock-api-mockito和powermock-module-junit4

    案例分析

    功能

    在一个数组中找出所有数最大的一个

    public class Largest {
    
     
    
        public static int largest(int[] list) {
    
           int index, max = Integer.MIN_VALUE;
    
    		if(list.length == 0) {
    
               throw new RuntimeException("Empty list!");
    
           }
           
           for(index = 0; index < list.length; index++) {
    
               if(list[index] > max) {
    
                  max = list[index];
    
               }
    
           }
    
           return max;
    
        }
    
    }
    

    输入

    一个整形数组

    输出

    整形数组的最大值

    测试用例

    简单数组

    以{7,8,9} 进行测试

    public class LargestTest extends TestCase {
    
     
    
        public LargestTest(String name) {
    
           super(name);
    
        }
    
       
    
        public void testSimple() {
    
           assertEquals(9, Largest.largest(new int[] {7, 8, 9}));
    
        }
    
       
    
    }
    

    相同元素

    public void testDup() {
    
           assertEquals(9, Largest.largest(new int[] {9, 9, 9}));
    
        }
    

    负数

    public void testNegtive() {
    
           assertEquals(-6, Largest.largest(new int[] {-7, -6, -9}));
    
        }
    

    数组为空

    public void testEmpty() {
    
           try{
    
               Largest.largest(new int[] {});
    
               fail("There should have thrown an exception");
    
           } catch(RuntimeException e) {
    
               assertTrue(true);
    
           }
    
        }
    

    注:当数组为空时,程序会抛出异常,使用测试代码捕获该异常判断是否为空

    junit测试案例

    junit断言

    JUnit提供的用于帮助开发者确定某个被测函数是否工作正常的辅助函数统称为断言。

    断言的每个方法都会记录是否失败了(断言为假)或者有错误了(遇到一个意料外的异常)的情况,并通过JUnit的一些类来报告这些结果。对于命令行版本的JUnit而言,这意味着将会在命令行控制台上显示一些错误消息;对于GUI版本的JUnit而言(如在eclipse里使用JUnit),如果出现失败或者错误,将会显示一个红色的条和一些用于对失败进行详细说明的辅助消息。

    当一个失败或者错误出现的时候,当前测试方法的执行流程将会被中止,但是(位于同一个测试类中的)其他测试将会继续运行。

    断言是单元测试最基本的组成部分。因此,JUnit程序提供了不同形式的多种断言。

    assertEquals

    assertEquals([String message], expected, actual);
    

    注:参数expected是开发者所期望的(通常是硬编码),actual是被测代码实际产生的值,message是一个可选的在发生错误时报告的消息。

    对于数组:

    public void testShuzuEquals() {
    
           int[] a = {1, 2, 4};
    
           int[] b = {1, 2, 4};
    
           assertEquals(a, b);
    
        }
    

    注:数组a,b内容相同,但因引用不同,运行此测试用例时还是在该断言处报错

    对于浮点数:

     assertEquals([String message], expected, actual, tolerance);
    

    注:需要添加精度tolerance

    assertNull 和 assertNotNull

    验证一个对象是否为null(或者为非null);message参数是可选的。

    assertNull([String message], java.lang.Object object);
    
     ([String message], java.lang.Object object);
    

    assertSame 和 assertNotSame

    验证expected参数和actual参数所引用的是否为同一个对象

    assertSame([String message], expected, actual);
    assertNotSame([String message], expected, actual);
    

    assertTrue

    验证给定的二元条件是否为真

    assertTrue([String message], Boolean condition);
    

    fail

    将会使测试立即失败,其中message参数是可选的。这种断言通常被用于标记某个不应该被达到的分支(例如,在一个预期发生的异常之后)。

    fail([String message]);
    

    使用断言

    一般而言,一个测试方法会包含多个断言。当一个断言失败的时候,该测试方法将会被中止,从而导致该方法中剩余的断言这次就无法执行了。此时开发者必须在继续测试之前先修复这个失败的测试。依此类推,开发者不断地修复一个又一个的测试,沿着这条路径慢慢前进。

    应当期望所有的测试在任何时候都能通过。在实践中,这意味着当引入一个bug的时候,只有一到两个测试会失败——这种情况下,把问题分离出来会相当容易。

    当有测试失败的时候,无论如何都不能给原有代码再添加新的特性。此时应该尽快地修复这个错误,直到让所有的测试都能顺利通过。

    junit测试结构

    单元测试类

    一个测试类包含一些测试方法;每个方法包含一个或者多个断言语句。
    但是测试类也能调用其他测试类:单独的类、包、甚至完整的一个系统。

    可以通过继承TestCase和添加@Test注解这两种方式来构建单元测试类;但如果想要使用test suite,则只能通过继承TestCase的方式构建单元测试类。

    若是继承TestCase的方式构建测试类,则无需在测试类中添加注解来声明某个具体的方法是:一个测试用例or每个测试用例执行之前的初始化操作集合or每个测试用例执行之后的清理操作集合or一个test suite or 每个test suite执行之前的初始化操作集合or每个test suite执行之后的清理操作集合;通过恰当的命名方式,这些对应的方法在该测试类被执行的时候就能被识别出来,如下的代码给出了说明:

    public class TestExample extends TestCase {
    
        public TestExample(String method) {
    
           super(method);
    
        }
    
       
    
        /*
    
         * 以setUp命名,该方法在每个测试用执行之前会被执行
    
         * 执行每个测试用例执行之前的一些初始化操作
    
         */
    
        public void setUp() {
    
           //...省略的代码...
    
        }
    
       
    
        /*
    
         * 以tearDown命名,该方法在每个测试用例执行之后会被执行
    
         * 执行每个测试用例执行之后的一些清理操作
    
         */
    
        public void tearDown() {
    
           //...省略的代码...
    
        }
    
       
    
        /*
    
         * 以“test”为前缀命名的方法,在继承自TestCase的类中自动被识别为1个测试用例
    
         */
    
        public void testCase1() {
    
           //...省略的代码...
    
        }
    
       
    
        public void testCase2() {
    
           //...省略的代码...
    
        }
    
       
    
        /*
    
         * 以oneTimeSetUp命名,该方法在每个test suite执行之前会被执行
    
         * 执行每个test suite执行之前的一些初始化操作
    
         */
    
        public static void oneTimeSetUp() {
    
           //...省略的代码...
    
        }
    
       
    
        /*
    
         * 以oneTimeTearDown命名,该方法在每个test suite执行之后会被执行
    
         * 执行每个test suite执行之后的一些清理操作
    
         */
    
        public static void oneTimeTearDown() {
    
           //...省略的代码...
    
        }
    
       
    
        /*
    
         * 以suite命名,在继承自TestCase的类中自动被识别为一个test suite
    
         */
    
        public static TestSuite suite() {
    
           TestSuite suite = new TestSuite();
    
           suite.addTest(new TestExample("testCase1"));
    
           suite.addTest(new TestExample("testCase2"));
    
           return suite;
    
        }
    
    }
    

    注:在这种方式构建的单元测试类,如果所有测试方法(测试用例)都不以“test”前缀来命名,则该测试类被执行的时候会报错

    若测试类没有继承自TestCase,则需通过加注解的方式将测试类中对应方法声明为:一个测试用例or每个测试用例执行之前的初始化操作集合or每个测试用例执行之后的清理操作集合;如下代码所示:

    public class TestExampleTwo {
    
        /*
    
         * @Before注解将注解方法声明为:
    
         * 执行每个测试用例执行前初始化操作的方法
    
         */
    
        @Before
    
        public void init() {
    
           //...省略的代码...
    
        }
    
       
    
        /*
    
         * @After注解将注解方法声明为:
    
         * 执行每个测试用例执行后清理操作的方法
    
         */
    
        @After
    
        public void clean() {
    
           //...省略的代码...
    
        }
    
       
    
        /*
    
         * @Test注解将注解方法声明为一个测试用例
    
         */
    
        @Test
    
        public void func () {
    
           //...省略的代码...
    
        }
    
    }
    

    自定义Junit断言、异常测试

    自定义Junit断言

    public class TestProject extends TestCase {
    
        /*
    
         * 自定义断言1——判断一个int型变量是否为偶数
    
         */
    
        public void assertEvenNumber(String message, int num) {
    
           int division = num / 2;
    
           int mod_num = num - 2*division;
    
           assertEquals(message, 0, mod_num);
    
        }
    
       
    
        /*
    
         * 自定义断言2——判断一个int型变量是否为奇数
    
         */
    
        public void assertOddNumber(String message, int num) {
    
           int division = num / 2;
    
           int mod_num = num - 2*division;
    
           assertEquals(message, 1, mod_num);
    
        }
    
       
    
        public void testCase() {
    
           assertOddNumber("Not Odd!", 3);
    
           assertEvenNumber("Not Even!", 16);
    
        }  
    
    }
    

    异常测试

    处理异常,需要分清以下两种情况

    • 从测试代码抛出的可预测异常;
    • 由于某个模块(或代码)发生严重错误,而抛出的不可预测异常。
    public void testEmpty() {
    
           try{
    
               Largest.largest(new int[] {});
    
               fail("There should have thrown an exception");
    
           } catch(RuntimeException e) {
    
               assertTrue(true);
    
           }
    
        }
    

    注:需要制造一个会引发异常的输入,测试能否捕捉到异常,若不能则用fail使该测试用例失败,否则(捕捉到异常)就使用assertTrue(true)声明该测试用例成功。

    展开全文
  • Android单元测试

    千次阅读 2021-12-02 14:09:14
    这两个 sourceSet 对应了不同的单元测试类型,同时两个 sourceSet 声明依赖的命令也有区别,前者是 testImplementation 后者是 androidTestImplementation。 app/src ├── androidTestjava (仪器化单元测试、UI...

    概述

    新建一个 module 的时候,Android Studio 自动帮我们生成了 test 和 androidTest 两个 sourceSet。这两个 sourceSet 对应了不同的单元测试类型,同时两个 sourceSet 声明依赖的命令也有区别,前者是 testImplementation 后者是 androidTestImplementation,在这篇文章中,我们主要讲本地单元测试。

    app/src
         ├── androidTestjava (Instrument单元测试、UI测试)
         ├── main/java (业务代码)
         └── test/java  (本地单元测试)
    

    一,本地单元测试

    顾名思义和 Android 无关,这种测试是和原生的 Java 测试一样,不依赖 Android 框架或者只有非常少的依赖,直接运行在你本地的JVM上,而不需要运行在一个 Android 设备或者 Android 模拟器上,所以这种测试方式是非常高效的,因此我们建议如果可以,就是用这种方法测试,比如业务逻辑代码,它们可能和 Android Activity 等没有太大关系。一般适合进行本地单元测试的代码就是:

    1. MVP 结构中的 Presenter 或者 MVVM 结构中的 ViewModel
    2. Helper 或者 Utils 工具类
    3. 公共基础模块,比如网络库、数据库等

    我们一直强调本地单元测试和 Android 框架没有关系,但是有时候还是不可避免地会依赖到 Android 框架,比如某些 Utils 工具类需要 Context,针对这种情况,我们只能使用模拟对象的框架了,1,如果使用 Java 语言开发推荐使用 Mocktio,如果使用 Kotlin 语言开发推荐使用 MockK;2,如果使用 Java 语言开发推荐使用 Mocktio,如果使用 Kotlin 语言开发推荐使用 MockK;3,如果使用 Java 语言开发推荐使用 Mocktio,如果使用 Kotlin 语言开发推荐使用 MockK。(重要的事情说三遍,都是血泪的经验)

    dependencies {
        // Required -- JUnit 4 framework
        testImplementation 'junit:junit:4.12'
        // Optional -- Mockito framework(可选,用于模拟一些依赖对象,以达到隔离依赖的效果)
        testImplementation "org.mockito:mockito-core:1.10.19"
    }
    

    下面看例子,新建一个名为 mylibrary 的Android Module,Android Studio 会自动帮我们在 src 目录下创建 test、androidTest、main 三个目录,该 module 的 build.gradle 默认配置如下,这里我们使用的是本地测试单元,所以先把 androidTestImplementation 的依赖注释掉:

    dependencies {
        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
        implementation 'androidx.core:core-ktx:1.6.0'
        implementation 'androidx.appcompat:appcompat:1.3.1'
        implementation 'com.google.android.material:material:1.4.0'
        testImplementation 'junit:junit:4.12'
        //androidTestImplementation 'androidx.test.ext:junit:1.1.3'
        //androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    }
    

    然后在 main 目录下 java 中定义一个 Utils 工具类,这个类有两个方法:

    package com.jdd.smart.mylibrary.util
    
    import java.util.regex.Pattern
    
    object Utils {
        /**
         * 是否有效的邮箱
         * */
        fun isValidEmail(email: String?): Boolean {
            if (email == null)
                return false
            val regEx1 =
                "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"
            val p = Pattern.compile(regEx1)
            val m = p.matcher(email)
            return m.matches()
        }
    
        /**
         * 是否有效的手机号,只判断位数
         * */
        fun isValidPhoneNumber(phone: String?): Boolean {
            if (phone == null)
                return false
            return phone.length == 11
        }
    }
    

    现在我们编写一个 Utils 类单元测试用例,这里可以使用AS的快捷键,选择对应的类->将光标停留在类上->按下右键>在弹出的弹窗中选择Generate->选择Test:
    在这里插入图片描述
    Testing library 选择 JUnit4,勾选 setUp/@Before 会生成一个带 @Before 注解的 空方法,tearDown/@After 则会生成一个带 @After 注解的空方法,点击 OK:
    在这里插入图片描述
    选择测试用例保存的路径,我们现在使用本地单元测试,所以放到 src/test/java 目录下,点击 OK ,然后测试用例就创建完成,UtilsTest 类中的方法一开始都是空方法,我们编写自己的测试代码:

    package com.jdd.smart.mylibrary.util
    
    import org.junit.Test
    
    import org.junit.Assert.*
    
    class UtilsTest {
    
        @Test
        fun isValidEmail() {
            assertEquals(false, Utils.isValidEmail("test"))
            assertEquals(true, Utils.isValidEmail("test@qq.com"))
        }
    
        @Test
        fun isValidPhoneNumber() {
            assertEquals(false, Utils.isValidPhoneNumber("123"))
            assertEquals(true, Utils.isValidPhoneNumber("12345678911"))
        }
    }
    

    测试用例编写完成,然后就是运行测试用例,有几种方法:

    1. 运行单个测试方法:选中@Test注解或者方法名,右键选择 Run
    2. 运行一个测试类中的所有测试方法:打开类文件,在类的范围内右键选择 Run 或者直接选择类文件直接右键 Run
    3. 运行一个目录下的所有测试类:选择这个目录,右键 Run
    4. 使用 gradle 命令:./gradlew :mylibrary:test ,然后在 mylibrary/build/reports/tests 目录下查看测试的结果
    5. 使用 AS 快捷键,打开右上角的 Gradle Tab,mylibrary -> Tasks-> verification->点击 test

    现在我们在 Utils 公共类增加一个“getMyString() ”的方法,这个方法需要一个 Context 对象:

     Utils/**
         * 获取 string
         * */
        fun getMyString(context: Context): String {
            return context.getString(R.string.mylibrary)
        }
    

    这时候就轮到 Mocktio 出场:

    1. 在 mylibrary 的 build.gradle 文件中添加 Mockito 库的依赖
    2. 在单元测试类定义 UtilsTest 的开头,添加 @RunWith(MockitoJUnitRunner::class) 注释
    3. 要为 Android 依赖项创建模拟对象,在要模拟的对象前添加 @Mock 注释
    4. 使用 Mockito 的 when() 和 thenReturn() 方法指定条件并在满足条件时返回期望的值
    5. 调用 Utils.getMyString() 方法,看看它返回的值和我们期望的值是否一样

    注意点:mock 出来的对象是一个虚假的对象,在测试环境中,用来替换掉真实的对象,以达到验证对象方法调用情况,或是指定这个对象的某些方法返回特定的值等。

    @RunWith(MockitoJUnitRunner::class)
    class UtilsTest {
        @Mock
        lateinit var mContext: Context
        private val FAKE_STRING = "Hello"
    
        @Test
        fun getMyString() {
            Mockito.`when`(mContext.getString(R.string.mylibrary)).thenReturn(FAKE_STRING)
            val myString = Utils.getMyString(mContext)
            assertEquals(FAKE_STRING, myString)
        }
    }
    

    我们注意到,在上面的测试用例 UtilsTest 中,我们使用了 when(….).thenReturn(….) API ,来定义当条件满足时函数的返回值,其实 Mockito 还提供了很多其他 API,接下来,我们介绍下Mockito。

    二,Mockito

    常用API

    1. verify().method Call,用来验证 mock 对象的方法是否被调用
    2. when(…​.).thenReturn(…​.),用来定义当条件满足时函数的返回值;对于无返回值的函数,我们可以使用 doReturn(…​).when(…​).method Call 来获得类似的效果
    3. doAnswer(…​).when(…​).method Call,用于有回调的函数,我们可以在 Answer 对象中拿到回调的对象,然后执行回调对象的方法
    4. 还有 doThrow() | doNothing() 等方法,可以参考 Mockito 的官方文档

    缺陷

    1. Mockito cannot mock/spy because : — final class : Mockito 预设是无法 Mock final class,而在 Kotlin 里任何 Class预设都是 final(除非使用 open 关键字)
    2. java.lang.IllegalStateException: anyObject() must not be null :Mockito 的 any() 、eq()等方法都是可能回传 null 的,而 Kotlin 是“空安全”的,显然它不能接受这些方法的
    3. Mockito 的 when()方法要加上反引号才能使用,这是因为 when 在 Kotlin 中是保留字
    4. Argument(s) are different! Wanted:Mockito 不能很好的支持 Kotlin 的 suspend functions

    第一条,可以依赖 mockito-inline 解决;第二条,可以依赖 mockito-kotlin 解决;第三条,只是语法问题还能接受;最后一条,要老命了,因为我们项目中大量使用了 Kotlin 的协程,Mockito 不能很好的支持挂起函数,那么项目中的异步操作就无法进行单元测试,怎么办,这就轮到另一款模拟框架 MockK 闪亮登场了。

    三,MockK

    MockK(mocking library for Kotlin),专为 Kotlin 而生 ,官方文档。MockK 其实跟 Mockito 的思路很像,只是语法稍有不同而已。
    我们还是用上面的 Utils 公共类举例,首先,依赖 MockK 库

    dependencies {
    	testImplementation 'junit:junit:4.12'
    	testImplementation "io.mockk:mockk:1.12.1"
    }
    

    然后,编写 getMyString() 方法的测试用例

    class UtilsTest {
        @MockK
        private lateinit var context: Context
        private val FAKE_STRING = "Hello"
    
        @Before
        fun setup() {
            MockKAnnotations.init(this)
            //另外一种 mock 对象的方法
            //context = mockk()
        }
    
        @Test
        fun getMyString() {
            every {
                context.getString(any())
            }.returns(FAKE_STRING)
            assertEquals(FAKE_STRING, Utils.getMyString(context))
            verify {
                context.getString(any())
            }
        }
    }
    
    1. 模拟 Context 对象,有两种方式@MockK 注解和 mockk() 方法,使用注解则必须在 @Before 方法中调用MockKAnnotations.init() 方法
    2. 使用 every(…).returns(…) 方法,定义当条件满足时函数的返回值,这个方法类似于 Mockito 的 when(…​.).thenReturn(…​.) 方法
    3. 调用 Utils.getMyString(context) 方法
    4. 使用 verify(…) 方法验证 Context 对象的 getString() 方法是否被调用

    常用API

    1. verify(…)、coVerify(…),验证 mock 对象的方法是否被调用
    2. every(…)、coEvery(…),定义当条件满足时函数的返回值,后面可以跟 returns(…) answers(…) throws(…) 等方法,可以去参考文档
    3. 以 co 开头的方法是配合 Kotlin 协程使用的,suspend 函数可以在方法的闭包内使用
    4. 推荐 API 文章 Kotlin 测试利器—MockK

    下面开始重头戏,项目实战走起,推荐一个很好的讲解 MockK 的系列

    四,项目实战

    我们项目使用的 Kotlin 协程 + MVVM,上面有提到,适合用本地单元测试的代码是 MVVM 结构中的 ViewModel,那么现在我们就为 ViewModel 编写测试用例。
    首先,我们要 在 build.gradle 中,添加单元测试需要的依赖:

    dependencies {
        testImplementation 'junit:junit:4.12'
        testImplementation "io.mockk:mockk:1.12.1"
        //对于runBlockingTest, CoroutineDispatcher等
        testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2'
        //对于InstantTaskExecutorRule
        testImplementation 'androidx.arch.core:core-testing:2.1.0'
    }
    //org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2 是用来测试 Kotlin 协程的
    //androidx.arch.core:core-testing:2.1.0 是用来测试 LiveData 的
    

    然后在 test/java 目录下,新增一个类,这个类很重要(Replace Dispatcher.Main with TestCoroutineDispatcher),为什么这么做?参考 Kotlin 的文章

    package com.jdd.smart.mylibrary
    
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.ExperimentalCoroutinesApi
    import kotlinx.coroutines.test.TestCoroutineDispatcher
    import kotlinx.coroutines.test.TestCoroutineScope
    import kotlinx.coroutines.test.resetMain
    import kotlinx.coroutines.test.setMain
    import org.junit.rules.TestWatcher
    import org.junit.runner.Description
    
    @ExperimentalCoroutinesApi
    class MainCoroutineRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()):
        TestWatcher(),
        TestCoroutineScope by TestCoroutineScope(dispatcher) {
        override fun starting(description: Description?) {
            super.starting(description)
            Dispatchers.setMain(dispatcher)
        }
    
        override fun finished(description: Description?) {
            super.finished(description)
            cleanupTestCoroutines()
            Dispatchers.resetMain()
        }
    }
    

    最后编写测试用例:

    class ProductViewModelTest {
    
        @get:Rule
        val instantTaskExecutorRule = InstantTaskExecutorRule()
    
        @ExperimentalCoroutinesApi
        @get:Rule
        val mainCoroutineRule = MainCoroutineRule()
    
        private lateinit var params: Params
    
        private lateinit var repository: ProductRepository
    
        private lateinit var viewModel: ProductViewModel
    
        @Before
        fun setup() {
            repository = mockk()
            params = mockk()
            viewModel = ProductViewModel(repository)
        }
    
        @ExperimentalCoroutinesApi
        @Test
        fun getList_SuccessTest() {
        	// 注意这里使用 runBlockingTest
            mainCoroutineRule.runBlockingTest {
                val result = Result.Success("hhhh")
                //定义条件和满足条件的返回值
                coEvery {
                	// getList 是挂起函数,返回值是 Result<String>
                    repository.getList(any())
                }.returns(result)
                viewModel.getList(params)
                //验证函数是否被调用
                coVerify {
                	// getList 是挂起函数
                    repository.getList(any())
                }
                //liveData 是 MutableLiveData ,验证 liveData 是否赋值成功
                Assert.assertEquals("hhhh", viewModel.liveData.value)
            }
        }
    }
    

    上面的例子是 MVVM 架构的项目,这篇文章是 MVP 架构的项目

    五,测试代码覆盖率

    Android Studio 支持的 Code Coverage Tool : jacoco、IntelliJ IDEA。上面有提到,当新建一个 module 时,Android Studio 自动帮我们生成了 test 和 androidTest 两个 sourceSet,在Android Studio中,在 androidTest 包下的单元测试代码,默认使用 jacoco 插件生成包含代码覆盖率的测试报告;而 test 包下的单元测试代码,则直接使用 IntelliJ IDEA 生成覆盖率报告,也可以通过自定义 gradle task 使用 jacoco 插件生成与 androidTest 相同格式的测试报告。在这篇文章中,我们主要关注如何生成本地单元测试覆盖率报告。

    1. IntelliJ IDEA

    参考上面讲的 “运行测试用例” 的几种方法,在 Run 命令下面,有一个 Run xxx with Coverage 命令,点击这个 Coverage 命令,就会生成覆盖率报告。
    在这里插入图片描述
    2. jacoco

    需要自定义 gradle task 。
    首先,新建一个 jacoco.gradle 文件,内容如下:

    apply plugin: 'jacoco'
    
    jacoco {
        toolVersion = "0.8.6" //指定jacoco的版本
    }
    
    //依赖于testDebugUnitTest任务
    task jacocoTestReport(type: JacocoReport, dependsOn: "testDebugUnitTest") {
        group = "reporting"指定task的分组
        description = "Generate Jacoco coverage reports"指定task的描述
        reports {
            xml.enabled = true
            html.enabled = true
            csv.enabled = false
        }
        //设置需要检测覆盖率的目录
        def mainSrc = "${projectDir}/src/main/java"
        sourceDirectories.from = files([mainSrc])
        // exclude auto-generated classes and tests
        def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', 'android/**/*.*']
        //定义检测覆盖率的class所在目录,注意:不同 gradle 版本可能不一样,需要自行替换
        def debugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter)
        classDirectories.from = files([debugTree])
        executionData.from = fileTree(dir: project.projectDir, includes: ['**/*.exec', '**/*.ec'])
    }
    

    注意:debugTree 配置不同 gradle 版本可能不一样

    然后,在 module 的 build.gradle 文件里依赖 jacoco.gradle 即可:

    apply from: 'jacoco.gradle'
    apply plugin: 'com.android.library'
    apply plugin: 'kotlin-android'
    

    Syns 完成后,在右上角的 Gradle Tab 会生成一个 task ,mylibrary -> Tasks-> reporting -> jacocoTestReport ,点击执行,就会生成覆盖率报告。
    在这里插入图片描述

    结束语

    感谢大家的阅读,我这里只是分享了一些自己踩过的坑。
    路漫漫其修远兮,吾将上下而求索,希望大家能共同探索、一起进步。

    展开全文
  • 测试用例-单元测试

    千次阅读 2021-11-09 14:35:01
    单元测试——编写手册 1.简述 本文主要针对如何使用Junit编写单元测试进行描述 文中的实例基于Junit 4 所谓单元测试,即是指针对程序中的一些单元进行测试的方法 这些单元在Junit中的最小单位为方法 借助单元测试,...

    单元测试——编写手册

    1.简述

    本文主要针对如何使用Junit编写单元测试进行描述
    文中的实例基于Junit 4

    所谓单元测试,即是指针对程序中的一些单元进行测试的方法
    这些单元在Junit中的最小单位为方法
    借助单元测试,我们可以轻松地单独测试程序中的某一个逻辑片段而不需要在意程序的外部依赖和其它逻辑
    
    接口测试单元测试
    只能以接口为维度进行测试只需被测试的单元逻辑正常即可
    工程必须编译通过并打包进行部署可以不依赖外部,测试进度不再受制于外部条件
    工程的外部依赖(数据库、调用的服务等)必须就绪可以以方法为维度进行测试
    难以测试复杂的逻辑分支,为测试数据需要调整各个数据源(数据库、缓存、消息队列)可以根据单元的逻辑复杂程度编排测试用例数量,测试使用的数据可以自由调整

    2.创建测试用例

    2.1工程准备

    1. 确保工程的maven依赖中包含junit,版本至少为4.12,一般包含在spring-boot-starter-test中;

    2. 确保工程的目录中包含src/test/java和src/test/resource;

      *部分工程没有src/test的相关包,需要手动创建
      *src/test/java主要用于存放测试用例和测试相关的类
      *src/test/resoutces主要用于存放测试使用的配置和资源文件

    2.2 编写测试启动类

    许多工程在启动时需要加载许多配置类,与外部系统进行连接,非常复杂
    为了使单元测试更加轻便,我们可以编写单元测试专用的启动类,屏蔽一些不相关的启动项
    若你的工程启动和外部连接依赖本身就很简单,可以省略这一步,不写测试启动类则执行测试时默认使用	src/main/java下的启动类
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.FilterType;
     
    @ComponentScan(basePackages = { "需要扫描的包名" }, excludeFilters = {
            @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = { 扫描的包中需要排除的类 }) })
    @SpringBootApplication(exclude = { 需要排除的一些启动时自动配置类 })
    public class TestApplication {
     
        public static void main(String[] args) {
            SpringApplication.run(TestApplication.class, args);
        }
    }
    

    这个启动类中常用的配置如下

    1. @ComponentScan
      用于扫描指定的包,excludeFilters用于排除扫描的包中不需要的Bean
    2. @SpringBootApplication
      用于排除一起自动配置的启动类,防止诸如数据库、Mongo等启动类进行外部连接
      测试的启动类与工程的启动类类似,它通常防止在src/test/java下的项目根包中

    其中“需要扫描的包名”和“扫描的包中需要排除的类”根据具体的工程决定,可按需将工程中的一些启动时配置类排除在外(Cache配置等),保证你测试的类以及它们的直接依赖被扫描到即可
    “需要排除的一些启动时自动配置类”可参考如下对照表按需排除

    作用全类名
    MongoDB自动配置org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
    org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration
    数据源自动配置org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
    Hibernate注解自动配置org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

    启动类的扫描配置决定了测试用例的范围以及启动速度
    可以按需编写多个不同扫描范围的启动类以适配不同的测试需求

    2.3 构建抽象测试类

    根据启动类创建一个抽象测试类是个好方法,它能够让你在创建测试用例时通过选择继承的父类直接确定使用的测试启动配置。

    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
    import org.springframework.test.context.junit4.SpringRunner;
     
    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = 启动类)
    public abstract class BaseTest {
     
    }
    

    @SpringBootTest
    用于指定测试的一些主要参数,主要是classes,用于指定关联的启动类,这样继承自这个类的所有子类都会使用相同的启动类

    2.4 使用测试专用的配置

    就像在工程部署时我们可以指定不同环境的配置文件一样;
    执行测试时我们也可以使用不同的配置文件;
    使用自定义测试启动类的情况下将配置文件放置在src/test/resource下则测试类默认会使用该包下的配置和资源文件;
    由于测试启动类中通常会排除许多与测试目标无关的类,所以测试专用的配置文件可以更加轻量化,哪些被排除的类所引用的配置都可以省略;

    2.5 新建Test类

    针对一个被测类创建一个测试类
    测试类通常以被测类的名字+Test进行命名,并放置在与src/test/java下与被测类同包名的包中
    
    1. 根据被测类创建测试类
    2. 配置测试类的参数,通常这些参数会按照约定给出一些默认值;
    3. 选择要测试的方法,注意:junit测试只能对非private方法进行测试DemoServiceTest,测试用例在src/test/java下的被测类同名包中;此时可以选择继承的父类,根据测试需要进行继承
    4. 生成测试类,得到一个单元测试类的骨架,包含了选择的方法的测试用例

    编写用例

    3.1 了解Mock测试

    在这里插入图片描述
    一个Bean通常会形成一个依赖树,这种发散的依赖结构导致我们在测试一个类的逻辑时其实是连同它所依赖的逻辑一同进行测试

    此时使用Mock测试就可以解决依赖过多,逻辑复杂的问题

    Mock以为模拟、虚拟,就是将原有的逻辑进行模拟,使用规划的Mock方案逻辑进行替代

    在Mock测试中,我们通常对测试对象的所有直接依赖进行Mock,被Mock的直接依赖将变成只有方法签名的空壳

    被调用时它们不会再调用间接依赖,也不会执行原有的逻辑,只会根据Mock方案进行返回

    这样在测试测试对象时我们就不在需要关心它负载的间接依赖关系和所有依赖的内部逻辑了,只需要专注于当前测试对象的逻辑即可

    3.2 完全Mock依赖

    这种测试仅测试测试对象内部的逻辑,屏蔽所有测试对象的依赖
    适用于测试内部逻辑较为复杂的对象,或依赖较为复杂的对象
    样例:
    在这里插入图片描述
    注意

    1. @MockBean标记的依赖的方法默认变为空方法且返回值为null,一定要配合mock方案才能执行逻辑和获取返回值(参照FAQ-@MockBean与@SpyBean)
    2. mock方案的编写参照FAQ-mock方案
    3. 需要调用@MockBean标记的类中无返回值的方法时可以不编写mock方案,但一定要校验调用是否执行(参照FAQ-验证调用是否执行)

    3.3 完全真实依赖

    这种类型的测试同时测试测试对象及全部其依赖的逻辑,针对实际调用流程进行测试
    适用于测试一个完整的流程以及测试调用链路正确性
    

    样例
    在这里插入图片描述
    注意

    1. 这种测试方式与Mock毫无关系,为常规的方法逻辑调用
    2. 测试的结果与输入的数据、持久化的数据、程序逻辑直接相关,因此需要事先准备好配套得入参、数据库脚本/Redis缓存并熟悉程序的逻辑,以此推导出正确的处理结果

    3.4 Mock与真实依赖相结合

    这种类型的测试同时测试测试对象及其部分依赖的逻辑,屏蔽部分较为复杂的依赖同时对实际调用流程进行测试
    适用于测试一个部分依赖较为复杂的流程以及测试调用链路正确性
    

    样例:
    在这里插入图片描述
    注意

    1. @SpyBean标记的依赖的方法默认为真实逻辑,若配合mock方案则在入参与mock方案一致时根据mock方案的设置进行返回(参照FAQ-@MockBean与@SpyBean)
    2. mock方案的编写参照FAQ-mock方案
    3. 需要调用@SpyBean标记的类的方法的真实逻辑时可以不编写mock方案
    4. 测试的结果与输入的数据、持久化的数据、程序逻辑、mock方案均相关,因此需要事先准备好配套得入参、数据库脚本/Redis缓存并熟悉程序的逻辑和mock方案的逻辑,以此推导出正确的处理结果
    5. 虽然@SpyBean标记的类在没有设置mock方案的情况下原则上与原类的逻辑一致,但由于底层实现原理的种种限制,它与原生的类在某些情况下不能完全一致;若想直接测试完全真实依赖的场景,请参照完全使用真实依赖的测试章节的描述

    3.5 混合测试

    对一个测试对象中的不同方法采取不同的测试策略
    对同一个测试对象的不同方法根据需求采取“完全Mock”或“完全真实”或“Mock与真实结合”的方式;没有对同一个测试	对象编写多个不同类型测试用例的需求
    

    注意

    1. 此种测试是其他3种的复合形式,单个用例内的书写参照相应章节的描述
    2. 此种测试的测试对象的依赖需要使用@SpyBean的方式进行标记,否则难以执行真实逻辑(参照FAQ-@MockBean与@SpyBean)

    4. FAQ

    4.1 @MockBean与@SpyBean

    在进行mock测试或混合测试时可以看到这2中不同的设定测试对象直接依赖的注解
    
    /相同不同
    @MockBean提供mock测试能力mock方案的作用域都在一个@Test内默认将标记该注解的类及其依赖的方法全部掏空,直接调用将没有任何执行逻辑并返回null(如果有返回值)
    @SpyBean提供mock测试能力mock方案的作用域都在一个@Test内默认将标记该注解的类以及依赖的方法保持原样,直接调用将按照方法原本的逻辑执行并返回;可以按照混合测试的需求灵活决定是否mock
    /使用场景
    @MockBean适用于标记的依赖需要被完全mock的场景,不配合mock方案使用易引发空指针异常
    @SpyBean适用于标记的依赖只有部分逻辑需要mock的场景,不配合mock方案则执行原方法,配合mock方案则执行mock方案

    4.2 mock方案

    在进行mock测试时需要提前针对被mock的逻辑进行规划
    参照如下写法:

    Mockito.doReturn(mockData).when(demoService).function(Matchers.eq(arg1), Matchers.eq(arg2));
    Mockito.doNothing().when(demoService).function(Matchers.eq(arg1), Matchers.eq(arg2));
    Mockito.doThrow(exception).when(demoService).function((Matchers.eq(arg1), Matchers.eq(arg2));
    
    关键词解释
    doReturn(mockData)模拟方法正常返回数据
    doNothing()模拟方法未执行(可用于没有返回值的方法)
    doThrow(exception)模拟方法执行时抛出异常
    demoService被@MockBean或@SpyBean标记的测试对象的直接依赖
    function、arg1、arg2直接依赖的方法名及其参数
    Matchers.eq()org.mockito.Matchers提供的一些更灵活的mock调用时参数验证方法;验证通过时方法返回模拟的返回数据,验证不通过时方法按照@MockBean或@SpyBean的默认策略执行

    4.3 同名方法调用多次

    同名方法设置多个mock方案时,不同入参预期的方案都会保留,相同入参预期的方案以最后一个为准
    例,设置了如下mock方案:

    @MockBean
    private DemoService demoService;
     
    @Test
    public void testFunction() {
        // 省略其他步骤,此处只展示mock方案设置
        // 方案1
        Mockito.doReturn(demoDataA).when(demoService).callFun(eq("alpha"));
        // 方案2
        Mockito.doReturn(demoDataB).when(demoService).callFun(eq("beta"));
        // 方案3
        Mockito.doCallRealMethod().when(demoService).callFun(eq("beta"));
    }
    

    方案1,3会生效,方案2会被方案3覆盖而失效
    调用demoService.callFun(String)方法时:
    若入参为"alpha",则返回demoDataA;
    若入参为"beta",则执行该方法的真实逻辑并返回;
    若入参为其他,则执行空方法并返回null(@MockBean的默认方案);
    在上述场景的基础上,若被测方法中存在同一方法调用多次且入参相同,但根据执行顺序返回值不同,则参照如下写法(使用链式调用依序排布多个方案):

    @MockBean
    private DemoService demoService;
     
    @Test
    public void testFunction() {
        // 省略其他步骤,此处只展示mock方案设置
        // 方案1
        Mockito.doReturn(demoDataA).when(demoService).callFun(eq("alpha"));
        // 方案2(包含多次调用的方案)
        Mockito.doReturn(demoDataB).doCallRealMethod().when(demoService).callFun(eq("beta"));
    }
    

    调用demoService.callFun(String)方法时:

    若入参为"alpha",则返回demoDataA;
    若入参为"beta"且是该入参的第1次调用,则返回demoDataB;
    若入参为"beta"且是该入参的第2次调用,执行该方法的真实逻辑并返回;
    若入参为"beta"且是该入参的第3次及以上调用,执行方案链中最后一个方案(例子中为调用真实逻辑);
    若入参为其他,则执行空方法并返回null(@MockBean的默认方案)

    4.4 验证调用是否执行

    在mock测试中需要验证mock方案标记的方法是否按照预期的入参执行过,若缺少这一步可能导致测试用例出现未预期的成功
    例,一个用例按如下步骤执行:

    1. 测试的依赖使用@SpyBean标注
    2. 执行到mock方案对应的方法,入参与mock方案预期不一致
    3. 方法按照真实逻辑执行
    4. 方法的返回值恰巧与预期一致
    5. 测试成功

    上述例子的用例可能会在某次数据库数据变动,外部链接中断的场景下执行失败
    增加验证调用是否执行的步骤可以解决该问题
    写法如下:

    Mockito.verify(demoService).function(Matchers.eq(arg1), Matchers.eq(arg2));
    

    与mock方案中的写法非常相似
    当同名方法调用多次且入参一致时采用如下写法:

    Mockito.verify(demoService, new Times(2)).function(Matchers.eq(arg1), Matchers.eq(arg2));
    
    展开全文
  • 相信单元测试是属于那种没有用过也听过的技术(如果你是大佬,听过也用过,欢迎提出宝贵的意见????‍♀️????‍♂️)。那么到底什么是单元测试单元测试在实际项目开发中能给我们带来什么样的好处?我们站在前端...
  • Java单元测试总结

    千次阅读 2021-06-13 15:50:20
    文章目录概述单元测试概念测试项目单元测试工具JUnitMockitoSpringBoot测试总结 概述 关于为什么要做单元测试是老生常谈了,越是小公司越不重视单元测试,这是实践出来的最经济的做法。由于大部分人在小公司工作,...
  • 单元测试、黑盒测试、白盒测试

    千次阅读 2021-12-05 10:25:46
    单元测试 概念:单元测试(Unit Testing)是对软件基本组成单元进行的测试,如函数或是一个类的方法。这里的单元,就是软件设计的最小单位。 单元测试的两个步骤:人工静态检查法与动态执行跟踪法。 人工静态检查是...
  • 自动化测试之单元测试框架

    千次阅读 2022-02-13 20:19:45
    单元测试框架 一、单元测试的定义 1:什么是单元测试? 2:为什么要做单元测试? 二、unittest框架以及原理介绍 1、unittest框架最核心的四个概念: 2、单元测试案例 三、编写测试用例 1、TestCase类编写...
  • 单元测试与集成测试

    万次阅读 多人点赞 2019-09-17 08:25:00
    按测试策略和过程,软件测试分为单元测试、集成测试、确认测试和系统测试。 按软件系统工程,测试是软件质量保证的最后的一关。 高质量的程序取决于以下几个方面: 高质量的设计 规范的编码 有效的测试 开发部...
  • 单元测试试模块测试和集成测试的基础是指对程序中单独的一个单元进行测试(类,或类的集合甚至是函数)
  • 在应用程序中,单元是具有一个或多个输入和单个输出的软件中最小可测试部分。...单元测试是自动化测试金字塔模型中占比最大的测试类型,做好单元测试对于保证软件产品的质量非常重要,单元测试可以: 及早发现软...
  • 单元测试->集成测试->系统测试->验收测试 单元测试 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。一个单元可能是单个程序、类、对象、方法等。 测试阶段:编码后或编码...
  • C# 单元测试(入门)

    千次阅读 2021-03-21 12:35:43
    XUnit 2.2.0 单元测试框架 xunit.runner.visualstudio 2.2.0 测试运行工具 Moq 4.7.10 模拟框架 什么是单元测试? 确保软件应用程序按作者的期望执行操作,其中最好的一种方法是拥有自动化测试套件。 可以对软件...
  • 单元测试 颗粒度最小,一般由开发小组采用白盒方式来测试,主要测试单元是否符合“设计”;是指 对软件中的最小可测试单元进行检查和验证 集成测试 介于单元测试和系统测试之间,一般由开发小组采用白盒+黑盒的...
  • 开源单元测试工具汇总

    千次阅读 2020-10-24 01:07:16
    开源单元测试工具/框架汇总 作为开源软件的爱好者,了解了单元测试这概念后,自然忍不住去找找有什么开源单元测试工具。 下面列出了30款1,不分先后顺序。 1. NoSQL的单元测试工具 NoSQLUnit 单元测试工具 授权协议...
  • 2015-02-23 06:30:01阅读( 174 )工厂在组装一台电视机之前,会对每个元件都进行测试,这,就是单元测试。其实我们每天都在做单元测试。你写了一个函数,除了极简单的外,总是要执行一下,看看功能是否正常,有时还要...
  • 如何正确编写单元测试

    千次阅读 2021-11-24 23:04:40
    国内的大多数互联网公司只注重软件功能,却往往忽略了极为重要的软件质量,在一个月以前,我认为遵循了代码规范(阿里规约、sonar)的软件系统已经算是一个质量比较好的软件系统了,但是在我了解单元测试以后,才...
  • 超级详细的Junit单元测试教程

    万次阅读 多人点赞 2020-05-31 23:23:23
    如果你只会使用@Test来完成单元测试,那你是时候该深入一下了,其实知识点一点都不少!
  • 单元测试基本写法

    千次阅读 2021-12-29 22:40:16
    单元测试 对单独的代码块(例如函数)分别进行测试,以保证它们的正确性 集成测试 对大量的程序单元的协同工作情况做测试 系统测试 同时对整个系统的正确性进行检查,而不是针对独立的片段 在众多的测试中,与程序开发...
  • 如何使用Python进行单元测试

    千次阅读 2021-11-25 17:20:26
    我是一个开发团队的一员,他们使用单元测试来验证我们的代码是否按照它应该的方式工作。 在本文中,我将通过讨论以下主题来研究如何使用Python创建单元测试单元测试基础 可用的Python测试框架 测试设计原则 代码...
  • Android 单元测试,从小白到入门开始

    千次阅读 2021-10-14 17:20:11
    随着 Android 应用越来越壮大,对应代码量显著增加,代码逻辑也日趋复杂,此时需要采取一定的行动去保证代码质量,减少逻辑漏洞等,于是严格地执行单元测试编写任务,将单元测试落实到平常开发和维护任务当中去,...
  • 如何写Java单元测试

    千次阅读 2021-01-19 10:23:06
    什么是单元测试  我们在编写大型程序的时候,需要写成千上万个方法或函数,这些函数的功能可能很强大,但我们在程序中只用到该函数的一小部分功能,并且经过调试可以确定,这一小部分功能是正确的。但是,我们同时...
  • 单元测试的基本测试方法

    千次阅读 2021-07-25 01:21:43
    1、单元测试的基本方法单元测试的基本方法有:人工静态分析、自动静态分析、自动动态测试,人工动态测试。人工静态分析:通过人工阅读代码来查找错误,一般是程序员交叉查看对方的代码,可能发现有特征错误和无特征...
  • spring boot单元测试

    千次阅读 2021-08-05 22:11:03
    做一个稳健的开发,写一首漂亮的单元测试是少不了的 首先要分清几个概念:测试方法、测试类、测试集、测试运行器。 测试方法就是用 @Test 注解的一些函数。 测试类是包含一个或多个测试方法的一个 XxTest.java ...
  • 单元测试之mock使用

    千次阅读 2022-04-10 20:34:51
    单元测试之mock使用
  • 单元测试】NUnit框架了解及使用

    千次阅读 2022-03-02 10:45:08
    何为单元测试(unit test) 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。 其实,对“单元”的定义取决于自己。如果你正在使用函数式编程,一个单元最有可能指的是一个函数。你的单元测试...
  • PHP单元测试

    千次阅读 2021-06-03 18:13:31
    什么是单元测试 百度百科: 百度百科:单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 744,429
精华内容 297,771
关键字:

单元测试