zoukankan      html  css  js  c++  java
  • 浅谈单元测试

    什么是单元测试

    单元测试本质上也是代码,与普通代码的区别在于它是验证代码正确性的代码。可简单做个定义:单元测试是开发人员编写的、用于检测在特定条件下目标代码正确性的代码。

    软件开发天生就具有复杂性,没人敢打包票说自己写的代码一点问题都没有,或者不经测试就能保证代码正确运行,可能你在这个执行路径下能够执行,殊不知还有其他路径,有一一去验证过吗,因此,要保证程序的正确性就必须要对我们代码进行严格测试。

    举个简单例子:比如有个计算类,里面有个 add 方法,操作就是两个数进行相加。

    public class Calculator {
        public int add(int one, int another) {
            //只是简单的两个数相加
            return one + another;
        }
    }

    常规做法:假如你写好了这个方法,你想进行验证 add 方法的正确性,需要写个使用 add 方法的 main 函数,首先实例化 Calculator 类,然后调用 add 方法并传入两个参数,比如 1 和 2。然后你运行这个工程,看得出结果是否为 3 ,如果是 3 ,则表明我这个方法写的没有错误,可能就不测试了,就继续开发后续的功能,如果不是 3 ,则返回去看看代码中哪里出错了,重新进行调试,甚至有时候肉眼还看不出代码哪里出错,此时就引入断点去查看,在此期间,很大一部分时间就花在断点、调试、运行上。

    单元测试做法:首先会利用 JUnit 测试框架(至于这个框架后面介绍)写一段测试代码,如下:

    public class CalculatorTest {
        public void testAdd() throws Exception {
            Calculator calculator = new Calculator();
            int sum = calculator.add(1, 2);
            Assert.assertEquals(3, sum);
        }
    }

    这里的 CalculatorTest 是 Calculator 对应的测试类,这里的 testAdd 对应着 add 的测试方法,进行测试一般分为三步骤:

    • setup。一般是 new 出你要测试的那个类,比如: Calculator calculator = new Calculator();
    • 执行操作。一般是调用你要测试的那个方法,获得运行结果: int sum = calculator.add(1, 2);
    • 验证结果。验证得到的结果跟预期中是一样的: Assert.assertEquals(3, sum);

    看到 Assert 这个关键词了吗,这里可以理解为断言或者期望值,根据入参的值,期望有个什么值输出,而不是靠肉眼去验证是不是自己想要的值,是直接通过判断值是否相等性来验证会具有更客观性。

    以上介绍的只是单元测试一点点,那它能给我们带来哪些更多好处呢?

    为什么要做单元测试

    通常我们在做任何工作会先考虑它的回报,编写代码更是如此。如果单元测试的作用不大,没有人会愿意再写一堆无用的代码,那么单元测试到底能够给我们带来什么优点呢?如下:

    • 便于后期重构。单元测试可以为代码的重构提供保障,只要重构代码之后单元测试全部运行通过,那么在很大程度上表示这次重构没有引入新的BUG,当然这是建立在完整、有效的单元测试覆盖率的基础上。
    • 优化设计。编写单元测试将使用户从调用者的角度观察、思考,特别是使用TDD驱动开发的开发方式,会让使用者把程序设计成易于调用和可测试,并且解除软件中的耦合。
    • 文档记录。单元测试就是一种无价的文档,它是展示函数或类如何使用的最佳文档,这份文档是可编译、可运行的、并且它保持最新,永远与代码同步。
    • 具有回归性。自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地地快速运行测试,而不是将代码部署到设备之后,然后再手动地覆盖各种执行路径,这样的行为效率低下,浪费时间。

    等等,讲了这么多优点,无非就是良好的接口设计、正确性、可回归、可测试、完善的调用文档、高内聚、低耦合,这些优点已经足以让我们对单元测试重视起来了,但是个人觉得还有更重要的原因。

    • 首先,带来自信。在接手一个新的项目,或者说是参与一个新的项目开发时,往往这种情况是你半途参加进去的,你需要对已有的代码结构进行解读和理解,对于业务的理解,对于代码个中各个模块关系的理解。如果一开始就理财出错,很可能修改后的代码会引起更多的BUG出现,到那时候又需要修复更多的BUG,改了一个地方,很有可能会莫名其妙地影响另外一个地方,这种现象是很常见的。还有一种情况,假设你修改的功能没问题,但是需要去测试验证,在测试的时候就需要考虑这个功能点它原有的测试路径有哪些,又需要一一去验证功能路径,以证明本次修改对于已存在的功能点不造成影响。这其中就存在着很大的时间成本,导致效率不高。那是否存在着这么一种方式,我需要修改我想改动的地方,不需要关心修改完之后它所造成的影响,也不需要关心它的测试回归性,有,此时就是单元测试登场的时候。写单元测试代码,可以让我自己写的代码足够自信,它是经得起考验的。
    • 其次,更快反馈。对于有一定编程经验的开发人员来说,当他拿到一个新需求的时候,首先想到的不是动手 Coding ,而是会先想想代码的结构,有些类,数据结构该是如何,然后才开始敲代码。如果没有单元测试,一般流程基本是这个模块功能全部写完才开始测试,比如利用 MVP 架构的功能。一般都是开始 Model 模块,然后完善 Presenter 模块,最后写 View 模块,等这几个模块都写完了,再把 APP 跑起来,验证自己写的功能模块是否符合需求,没有符合则继续回去修改代码,这中间需要花费很长的时间才能知道当下自己写的代码是否符合要求,是否正确。那有没有一种即时反馈的方式呢,有,写单元测试即可,当你写完一个函数,马上就匹配一个单元测试函数,这样即写即测的方式可以保证你当场写的代码马上进行修改,测试通过一个,就表示完成一个小的功能点,最后,把函数组装起来,就是我们想要大的功能点。
    • 最后,节约时间。对于 Android 开发来说,一遍一遍的运行 APP ,然后执行相应的用户操作,看界面是否正确的显示,通过这种方式来测试功能,其实是非常浪费时间,而且效率不高,而用单元测试,可以几乎不用打开 APP 来执行,当然有些需要一些资源文件的是需要 APP 运行条件,绝大部分的功能在单元测试阶段就能验证完毕,那么速度就相对快很多。此外,单元测试还能帮忙减少 BUG ,从而减少调试 BUG 的时间,一些低级犯的错误在单元测试阶段就能避免掉。

    主流框架 JUnit 和 TestNG

    JUnit 是一个 Java 语言的单元测试框架,它是 xUnit 单元测试架构体系的一个实例,用于编写和运行可重复的测试。它包括以下特性:

    • 用于测试期望结果的断言(Assertion)
    • 用于共享共同测试数据的测试工具
    • 用于方便的组织和运行测试的测试套件
    • 图形和文本的测试运行器

    TestNG 是一个测试框架,其灵感来自 JUnitNUnit ,但引入了一些新的功能,使其功能更强大,使用更方便。TestNG 消除了大部分的旧框架的限制,使开发人员能够编写更加灵活和强大的测试。 因为它在很大程度上借鉴了Java注解( JDK5.0 引入的)来定义测试,它也可以显示如何使用这个新功能在真实的Java语言生产环境中。

    特点如下:

    • 注解
    • TestNG 使用 Java 和面向对象的功能
    • 支持综合类测试(例如,默认情况下,不用创建一个新的测试每个测试方法的类的实例)
    • 独立的编译时测试代码和运行时配置/数据信息
    • 灵活的运行时配置
    • 主要介绍“测试组”。当编译测试,只要要求 TestNG 运行所有的“前端”的测试,或“快”,“慢”,“数据库”等
    • 支持依赖测试方法,并行测试,负载测试,局部故障
    • 灵活的插件 API
    • 支持多线程测试
  • 相关阅读:
    从零开始的Python学习Episode 15——正则表达式
    从零开始的Python学习Episode 14——日志操作
    从零开始的Python学习Episode 13——常用模块
    从零开始的Python学习Episode 12——迭代器&生成器
    从零开始的Python学习Episode 11——装饰器
    从零开始的Python学习Episode 10——函数
    从零开始的Python学习Episode 9——集合
    从零开始的Python学习Episode 8——深浅拷贝
    Linux基础学习
    从零开始的Python学习Episode 7——文件基本操作
  • 原文地址:https://www.cnblogs.com/yuanjia2717/p/10344440.html
Copyright © 2011-2022 走看看