07.单元测试

0 阅读8分钟

单元测试

1.自动化测试

问题

基于测试的目的我们会发现我们即使不使用任何的工具,不使用任何的框架,即使不知道单元测试集成测试,我们也可以在代码上线之前对它进行验证,那需要大量的人工去操作,然后去对真实用户进行一个模拟然后把这个结果的期望进行一个验证

介绍

我们作为软件工程和开发者我们肯定不允许这种过多的人工参与的部分所以就会有自动化测试的出现,我们需要把这种测试的流程通过一些自动化的手段脚本或者是工具框架,来把它做成可以自动执行的可以很快反馈的一些测试类型

单元测试就是整个自动化测试过程中最基本使用率最高覆盖率最多的一种测试的类型

选择策略

如何去选择,下面这张图从成本和速度两个方面给我们做了一些指导,是更多覆盖面更广的单元测试(速度快成本低),然后更少覆盖的链路更核心的集成测试和对UI的测试(速度慢成本高)

0b6f8efffebd40cbb801d444d9e38151.png

2.介绍

单元测试是针对程序模块(软件设计的最小单位)来进行正确性校验的测试工作

可以理解为单元测试就是一个函数,利用不同的Case对某一个函数返回的结果进行是否符合预期的判断

3.误区

软件测试乃至单元测试并不是JUnit团队的发明。单元测试这个术语最初用于描述检查单个工作单元(一个类或一个方法)行为的测试。随着时间的推移,这个术语的使用范围扩大了。例如,电气电子工程师学会(IEEE)将单元测试定义为对单个硬件、软件单元或一组相关单元的测试

4.方法选择

狭义单元测试:狭义单元测试是针对一个类的一个公有、私有、保护方法的测试

广义单元测试:广义单元测试是针对一个类的一个公有方法的测试。首先,对于类的使用者来说,大多数情况下只能调用公有方法;其次,公有方法可能有私有、保护方法组成,独立存在的私有、保护方法是没有意义的;最后,在测试公有方法时,已经附带着测试私有、保护方法

在日常的单元测试中,大多使用广义单元测试,针对公有方法进行测试。当然,为了覆盖某些代码分支,也可以采用狭义单元测试,直接针对私有、保护方法进行测试

5.传统测试方式

背景

在没有接触单元测试之前我们是怎么做测试的?一般有两个方法:

  • 启动整个应用,像用户正常操作一样,先单击界面上的按钮,然后查看程序的反应
  • 在代码的某个地方写一个临时入口,例如在Java的main方法中测试某个方法或者某个类,用完留在项目中或删除

问题

  • 启动整个应用:每次测试都得启动整个应用,项目稍微大一点响应就会变得非常慢,如果面对的是PHP、Node.js等脚本语言还好,如果是Java、C++这类编译型语言则会非常痛苦
  • 临时入口:不删除会让项目变得很乱,删除的话下次想测试又得再次编写
  • 没法保留测试数据的创建过程,且场景、边界的覆盖基本随缘

6.为什么要写

asdasdsadojasdio.png

7.适用场景

  • 代码复用率:代码复用率越高,越有必要推行单测,越有必要提升单测的要求。因此这些代码被很多业务引用,因此其一旦有问题便会影响很多业务方,在这样的代码推行单测是收益较高的
  • 业务变化率:业务变化越快,越不适合用单测。如果业务变化非常快,一个单测的内容上线了没几天就又要修改,那么你不仅仅需要修改业务代码,还需要修改单测代码,那就是双倍的工作量了
  • 人员变化率:人员变化率指的是某个模块负责人的变化情况。如果某个模块的负责人经常变来变去,那么也是不太适合推行单测的。因为新负责的人需要花大量的时间去熟悉单测的内容,这会导致需求开发的时间变得非常长
  • 业务重要性:越是核心的业务,越有必要推行单测,并且越有必要以高标准要求。因为核心业务的稳定性、健壮性对于公司来说肯定非常重要,而单测确实是能够在最小单元去提升系统稳定性和系统健壮性

8.原则

First

asd113.png

139f3002e5134658b6173b051280ae79.png

BCDE

1774081226227.png

9.与集成测试的区别

什么是集成测试

单元测试和集成测试使用的测试框架和工具大部分是相同的

首先需要达成一致的是,无论是单元测试还是集成测试,它们都是自动化测试。为了更好地区分,我们可以这样理解。和生产代码以及单元测试代码在同一个代码仓库中,由开发同学自己编写的,对外部环境(数据库、文件系统、外部系统、消息队列等)有真实调用的测试就是集成测试

区别

下表中也从各种角度来对比了单元测试、集成测试和系统级别测试的区别

6ce3515b40d84221969b27be9c791e44.png

88d164317af042de99572b45414b3e91.png

10.为什么要使用测试框架

理论上不使用任何测试框架也可以实现单元测试,最初的单元测试就是这样实现的。不过现在利用xUnit等框架可以更方便地运行测试。使用框架的单元测试有以下好处:

  • 方便批量运行和管理测试
  • 可使用@Before等钩子实现数据准备、数据清理
  • 可通过断言验证结果,避免人工判定结果
  • 可通过覆盖率统计工具来统计代码测试的覆盖率
  • 可通过模拟解决代码之间相互依赖的问题

11.Java单元测试约定

在Java项目中,根据Maven项目结构有如下约定:

  • 测试用例的方法名称使用下划线,并且要表达出一个完整的句子,单元测试可以作为活文档
  • 测试文件要和业务代码类一一对应,并且要放置到和业务代码同级的测试模块中,例如src/test/java,这样IDE工具才可以自动识别,并通过快捷键跳转
  • 测试文件要和业务代码文件同名,且应使用Test结束
  • 要按照Given...When...Then 的风格组织测试代码
  • 测试用例必须有充分的断言语句(杜绝System.out)
  • 不需要特别的设置就能运行单元测试
  • 不允许注释单元测试的方法,如果需要快速跳过,使用@Ignore注解
  • 如果改变了全局对象,要使用@After注解清理,保持独立原则
  • 如果被测试的类实现了接口,尽量通过接口的类型测试
  • 配置合适的测试覆盖率,要为不同的代码配置不同等级的测试覆盖率。核心代码的测试率应为100%
  • 通用的准备工作使用相关生命周期注解,避免重复
  • 通用的数据准备工作可以通过提取一个测试助手类来完成
  • 提交代码前保证单元测试已通过
  • 修改业务代码时,要同步修改单元测试,并补充足够的测试用例

12.示例理解

步骤

  1. 构建测试数据
  2. 执行方法
  3. 断言

案例理解

c78321e21d684983aeb7bbacd6000359.png

13.快速开始

介绍

快速体验一下基于JUnit4写一个单元测试,注意这个单元测试不符合规范,只是为了快速跑通一个示例

添加依赖

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
</dependency>

注意:将dependency中的scope属性设置为test,这样在构建产生的软件包中会自动排除JUnit的Jar包

准备被测试的方法

public class HelloWorld {
    public static String hello() {
        return "Hello, world!";
    }
}

编写单元测试并运行

image21764645458661.png

14.实践经验

  • 学习一些简单的单元测试方法后,马上应用到项目中,再按照需要学习其他技巧
  • 根据二八原则,80%的代码都是很好测试且性价比高的,可优先选择为它们编写测试
  • 不必苛求测试覆盖率,有一些代码测试覆盖率很难提升,追求100%的代码测试覆盖率性价比非常低
  • 刚开始尽量使用主流或者平台内置的框架或库,例如IntelliJIDEA 可以很容易地引入JUnit。选用JUnit是非常划算的事情,它自带了Hamcrest断言库,因此没有必要一开始就使用AssertJ等更复杂的断言库