单元测试最佳实践(Java 实现)

image

一、概述

单元测试金字塔

单元测试是指对软件中的最小可测试单元进行检查和验证。单元在质量保证中是非常重要的环节,根据测试金字塔原理,越往上层的测试,所需的测试投入比例越大,效果也越差,而单元测试的成本要小的多,也更容易发现问题。

1.1 单元测试1.0架构

服务分层架构

以中台某应用为例,应用部署是微服务架构,对外提供dubbo服务,当前的单元测试,采用了分层测试框架,根据代码的分层,分为Service 层测试,Biz层测试,外部服务访问层测试,DAO 测试,Redis 访问层测试,每一层均使用mock 框架屏蔽下层的具体实现。

1.2 单元测试的过程

单元测试过程

单元测试的编写,主要包含以下几个阶段:

  1. 数据准备:在编写测试用例前,需要依赖到一些数据,数据来源一般是数据库,而构造数据,又不能依赖DAO层的代码,需要使用原生jdbc 去插入数据,测试代码编写效率低。
  2. 构造参数及打桩(stub):调用方法需要传递入参,有时候一个入参十几个参数需要set,set 方法写完,代码已经写了十来行了。
  3. 执行测试:这一步比较简单,直接调用被测方法即可。
  4. 结果验证:这里除了验证被测方法的返回值外,还需要验证插入到数据库中的数据是否正确,某外部方法被调用过n次或未调用过。
  5. 必要的清理:对打桩进行清理,对数据库脏数据进行清理。

二、 痛点

1. 重构代码需要改写大量单元测试用例

对外的Service接口在不变的情况下,对内部实现进行重构,这时候头痛的问题来了,大量的Service 层单元测试,biz层单元测试都要重写;有时候Service调用biz层接口时,参数传错了,而由于开发人员编写单元测试时不规范,参数匹配使用了 anyxxx(),导致参数传错的bug未被发现。

2. 测试库数据随意修改导致的单元测试不稳定

DAO层单元测试直连测试库,由于测试库的数据可以被任意修改,从而导致测试依赖的数据被更改,单元测试不通过,另外开发在编写单元测试时,没有清理意识,导致测试库大量垃圾数据。

3. 单元测试结果校验缺失

例如一个SaveItem()接口,执行完成后除了要验证执行成功以外,还应该验证落库数据的正确性,而编写这部分测试代码需要大量的使用原生jdbc 接口查询sql,并逐字段验证正确性,代码编写效率低下。

三、几个常用的测试框架的简介

1. 数据层单元测试框架 DbUnit

可以优雅的构造DB层的初始化数据,例如:

<?xml version='1.0' encoding='UTF-8'?>  
<dataset>  
    <employee employee_uid='1' 
          start_date='2001-11-01'           
          first_name='Andrew' 
          ssn='xxx-xx-xxxx' 
          last_name='Glover' />
</dataset>  

其中employee 是要构造数据的表名,后面的键值对是列名及对应的值,需要注意的是,第一行必须包含完整的字段名,否则加载的数据中全部会缺失某些字段。

2. 嵌入式的内存数据库 H2

非常适合在测试程序中使用,程序关闭时自动清理数据,H2 数据库的表结构初始化是通过 jdbc:initialize-database 标签实现的,单元测试中使用h2数据库非常简单,仅需修改jdbc连接即可。 引入依赖:

<dependency>  
     <groupId>com.h2database</groupId>
     <artifactId>h2</artifactId>
     <version>1.4.191</version>
     <scope>test</scope>
</dependency>  

数据源连接:

spring.datasource.url=jdbc:h2:mem:test  
spring.datasource.driver-class-name=org.hsqldb.jdbcDriver  
spring.datasource.username=root  
spring.datasource.password=  

schema 初始化:

<jdbc:initialize-database data-source="dataSource" ignore-failures="NONE">  
        <jdbc:script location="classpath:h2/schema.sql" encoding="UTF-8"/>
    </jdbc:initialize-database>

3. Spring小扩展 springockito

它简化了在集成测试的相关上下文XML文件中创建mockito mocks的方法。

<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mockito="http://www.mockito.org/spring/mockito"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.mockito.org/spring/mockito http://www.mockito.org/spring/mockito.xsd">
...
    <mockito:mock id="accountService" class="org.kubek2k.account.DefaultAccountService" />
..
</beans>  

4. spring 官方测试框架 spring-test

目前主流的开发框架都在使用spring 框架管理bean,在测试代码中,我们通用期望能够使用spring 框架,spring-test 框架帮助我们解决bean 的注入问题。

@ContextConfiguration(locations = "/test-context.xml", 
                        loader = SpringockitoContextLoader.class) 
public class CustomLoaderXmlApplicationContextTests {  
    // class body...
}

"/test-context.xml" 指定了测试类运行需要加载的 spring 配置文件路径,SpringockitoContextLoader 指定了加载配置的类,这两个一起用可以支持在使用 spring xml 配置的同时可以将 mockito 生成的 mock 对象 bean 注入 spring 上下文中。

5.支持静态方法mock的mock框架 powermock

支持静态方法 mock,同时兼容 mockito,powermock 示例:

@RunWith(PowerMockRunner.class)
@PrepareForTest( { YourClassWithEgStaticMethod.class })
public class YourTestCase {  
...
}

四、基于springtest+ut+powermock 的测试框架

单元测试框架

单元测试框架,数据库层使用h2数据库代替测试库,隔离单元测试数据与测试库数据,在单元测试结束后自动清理数据,避免污染测试库数据及被测试库数据影响,基于DbUnit 可以通过xml构造DB层初始化数据,实现测试代码与测试数据分离,依赖spring jdbc的初始化脚本初始化h2数据库的表结构。

1. 数据准备

单测依赖的Db数据,通过添加测试方法监听器,在 Junit 执行前通过 DbUnit 工具类,加载初始化文件,写入H2 数据库;单测的入参,通过 param.json 文件,以json格式编写入参数据,利用工具类读取文件并 json 反序列化为目标 Class 实例。
H2 数据库的表结构,则是通过上文提到的 jdbc:initialize-database 初始化的,开发同学必须保证此schema 与线上结构的一致性,否则会导致单测失败。

添加方法监听器

还有 41% 的精彩内容
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
支付 ¥9.99 继续阅读
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351