为了提高代码和产品质量,我们网站的大部分产品都接入了单测平台,任何任务在提测前都要执行整个提测工程的单元测试,测试结果全部通过并且覆盖率统计等指标达标之后才能通过提测申请。这当然对于提高产品代码质量是有非常大的好处的,有助于识别代码中的bug。但是在执行的过程中可不是那么顺利的,主要体现在有时单测执行失败并不是代码有什么bug,而是单测依赖的数据发生了变化导致单测的执行结果和预期的结果不一样assert失败,或者单测中依赖了其它模块的接口,下游环境出现了异常导致了单测执行失败。事实上在我们的开发任务中消耗了大量的时间来解决这两方面的单测问题,有时候可能编写代码只用了一个小时,但是折腾单测却会花掉一天,效率低下。对于第二种情况主要的做法是在单测中把依赖的远程接口mock掉,有一些开源的mock框架如Mockito、EasyMock可以使用,这里不多展开。这里主要探索一下第一种情况即单测依赖的数据不稳定造成单测结果不稳定。

平台每天最少要跑几十次单测,所以跑完单测之后最好要清理现场还原数据,否则测试库每天会多出很多垃圾数据。目前大多数做法都是按照下列步骤:
1.单测开始前准备数据插入数据到测试库;
2.执行单测逻辑;
3.还原现场,数据回滚。

这种做法大部分情况下是非常OK的,但是有些情况还是会造成问题:
1.虽然单测前构造了自己的测试数据,但是不能完成排除掉测试数据变化对测试结果造成的影响,比如有个case是要查询用户的最近的10个订单,或者是查询用户总共的订单数量,一旦有人用相同的测试账号新增了订单,那么就可能会导致测试case结果预测失败,assert报错。
2.单测结束后要回滚数据,要达到这个目的就要把测试代码嵌入到事务上下文中,这种做法实际上是变相的修改了执行逻辑,如果使用了mybatis框架,由于事务缓存的作用在某些情况下会产生问题,比如使用了oracle的sequence,在代码中执行了超过一次的sequence的nextVal,如果单测套上了事务,那么执行这段逻辑的时候后面不管查多少次nextVal,查出来的值跟第一次的值是一样的,因为后面几次查询都被缓存拦住了,这样会出现一些奇怪的问题。

hsqldb是一款纯Java编写的免费数据库,并且支持Memory-Only模型,所以每个单测使用独立的数据库就有了可能,Memory-Only模型数据不会持久化跑完即销毁,所以理论上可以解决上面两个问题。
因为我们正式的业务使用的都是mysql或者oracle数据库,要在单测中引入hsqldb,那么必然要让单测执行过程中读写指向到hsql数据库,这就涉及到db替换的过程。首要有一点很重要的是这个替换DB的过程不能侵入到业务代码中,不能因为单测的便利而给业务代码带来额外的风险。好在在maven工程中,单测代码和resource都可以跟业务代码隔离。我们可以定义一个额外的单测入口配置,并且利用spring容器中“同名的bean后注册的覆盖先注册”的这个规则来替换DB数据源。
先定义个单测入口配置文件ut_entry.xml,然后在单测class中import这个配置。

@ContextConfiguration({ "classpath:ut_entry.xml" })
public class BaseTestCase extends AbstractTestNGSpringContextTests {//......
}

在配置中嵌入hsqldb数据库:

<jdbc:embedded-database id="rdsMockDataSource"><jdbc:script location="classpath:db_mock/rds/rds_hsql.script"/></jdbc:embedded-database>

embedded-database支持几种type:HSQL、H2、DERBY,默认type是HSQL。
在rds_hsql.script添加需要创建的表等DDL SQL,这些SQL会在HSQL数据库初始化时执行:

CREATE TABLE tb_authenticate_other (id bigint NOT NULL AUTO_INCREMENT,auth_Id varchar(60) NOT NULL,account_id varchar(60) NOT NULL,id_card_name varchar(516) NOT NULL,id_card_num_enc varchar(1024) NOT NULL,id_card_num_md5 varchar(64) NOT NULL,id_card_photo_enc varchar(1024) DEFAULT NULL,user_id bigint NOT NULL,create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ;create unique INDEX uk_auth_Id on tb_authenticate_other(auth_Id);
create INDEX uk_account_num_Id on tb_authenticate_other (account_id,id_card_num_md5);

上面是用HSQL来替换MySQL数据库,可以变相的支持MySQL中HSQL不支持的语法,比如HSQL不支持MySQL的on update语法,可以通过触发器来变相的支持,增加下面的脚本:

CREATE TRIGGER trig_authenticate_other AFTER UPDATE ON tb_authenticate_otherreferencing NEW ROW AS newrowFOR EACH ROW WHEN (newrow.update_time IS NULL)UPDATE tb_authenticate_other SET update_time = CURRENT_TIMESTAMP;

如果使用了一些HSQL中不支持的MySQL函数,也可以在该脚本中处理,自定义一个一模一样的函数,比如你需要一个日志格式函数:

create function DATE_FORMAT(t TIMESTAMP, pattern VARCHAR(60)) returns TIMESTAMPreturn t;

然后定义一个和业务配置同名的sqlSessionFactory和TransactionManager,把hsqldb数据源注入到新定义的sqlSessionFactory和TransactionManager中:

<bean id="rdsSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="configLocation" value="classpath:mybatis-config-rds.xml"/><property name="dataSource" ref="rdsMockDataSource"/></bean><bean id="rdsTransactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="rdsMockDataSource"/><property name="nestedTransactionAllowed" value="true"/></bean><tx:annotation-driven transaction-manager="rdsTransactionManager"/>

Spring容易运行在不同的xml中定义同名的bean,并且后加载的bean会覆盖先加载的bean,所以就达到了运行单测时替换数据源的目的。

如果你的应用中使用了oracle数据库,那么可以按照这种步骤再定义一个mock oracle数据库的HSQL数据源。使用步骤还是很简单的,接下来就可以愉快的写单测代码并且愉快的跑单测了。这里再安利一个自动生成数据的框架podam,爱偷懒的可以去百度尝试一下。

hsqldb使用方便,内存模式执行效率高,能够提高单测的稳定性。但是使用的过程中还是发现一些问题:
1.mysql中使用了CURRENT_TIMESTAMP()的sql用hsqldb执行会报错,自定义function也不行,因为CURRENT_TIMESTAMP在hsqldb中也是个关键字。由于mysql中CURRENT_TIMESTAMP和CURRENT_TIMESTAMP()效果是一样的,所以可以把业务代码中的sql改成CURRENT_TIMESTAMP解决这个问题。
2.hsqldb的数据类型和mysql、oracle有差异,比如没有oracle中的number类型,tinyint、timestamp等类型不能指定长度等。
3.不支持oracle的connect by语法。
4.merge语法的update分支不能有where语句,而oracle是支持的。
5.在处理mybatis的keyColumn功能时,字段名称是大小写敏感的且只能用大写的,比如下面这样的,只能把id都改成大写的ID,这应该是hsqldb客户端的bug。

<insert id="xxx" parameterType="xxx" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
</insert>

总体来说,跟mysql的兼容性要好一些跟oracle的兼容性差一些,项目中是否使用hsqldb要结合它带来的收益和付出的代价做综合考量。

单元测试引入hsqldb探索相关推荐

  1. php带参数单元测试_一文搞定单元测试核心概念

    基础概念 单元测试(unittesting),是指对软件中的最小可测试单元进行检查和验证,这里的最小可测试单元通常是指函数或者类.单元测试是即所谓的白盒测试,一般由开发人员负责测试,因为开发人员知道被 ...

  2. net core WebApi——使用xUnits来实现单元测试

    前言 从开始敲代码到现在,不停地都是在喊着记得做测试,记得自测,测试人员打回来扣你money之类的,刚开始因为心疼钱(当然还是为了代码质量),就老老实实自己写完自己跑一遍,没有流程没有规划没有测试文档 ...

  3. 如何高效编写Go单元测试(一)

    前言 单元测试是用来对一个模块.一个函数或者一个类来进行正确性检验的测试工作.我们根据它来检验代码的行为是否和预期的一样,如果单元测试不通过,要么代码有bug,要么测试条件输入不正确,总之,需要修复使 ...

  4. 单元测试基础上篇——几大常用框架对比

    本文介绍了android里常用的几种单元测试框架:JUnit.Mockito.PowerMock.Robolectric.Espresso 各自的特点和适用场景. JUnit JUnit是Java编程 ...

  5. 探索式测试--第五章(混合探索式测试法)--读书笔记

    场景和探索 本章将展示如何把探索式测试的思维方法与传统的基于场景的测试方法结合起来.这种混合技术充分利用了探索式测试的指导思想,它打破了脚本测试法所固有的那种死板,提供了更多的灵活性. 因为用户不可能 ...

  6. 单元测试的五个主要准则

    自动化测试是所有大型软件项目不可或缺的一部分.它是提高质量.生产力和灵活性的一种手段.因此,对系统架构进行合理地设计以便利后续的开发和自动化测试变得至关重要. 自动化测试的好处 1.质量得以提高 因为 ...

  7. 不四:产品工程师的修炼之路

    简介:我是不四,毕业后一直在阿里和蚂蚁工作,不四是我在阿里的花名,社区中一般以另一个花名 "死马" 出现.每一个人的成长轨迹都不一样,一路上遇到的机遇也各不相同,这次分享也仅站在一 ...

  8. MongoDB学习指导

    原文地址:http://blog.csdn.net/jakenson/article/details/7060431 MongoDB的内部构造<MongoDB The Definitive Gu ...

  9. 关于Mongodb的全面总结

    MongoDB的内部构造<MongoDB The Definitive Guide> MongoDB的官方文档基本是how to do的介绍,而关于how it worked却少之又少,本 ...

  10. 关于Mongodb的全面总结,学习mongodb的人,可以从这里开始!

    转载地址:http://blog.csdn.net/he90227/article/details/45674513 原文地址:http://blog.csdn.NET/jakenson/articl ...

最新文章

  1. Ganglia 应用实践
  2. 解决删除从表,主表存在外键的问题
  3. 数据库事务的隔离级别简单总结
  4. [转]删除表中重复记录
  5. 《大数据》第2期“应用”——医疗健康大数据:应用实例与系统分析
  6. android 版本更新
  7. rfid 标签内存_RFID有源与无源的区别与联系
  8. Swift之 ? 和 !
  9. eNSP------三层交换机配置(拓扑图+命令)
  10. Python+FFmpeg提取哔哩哔哩安卓缓存
  11. Samba 实现文件共享
  12. 揭秘Apple Watch心率监测技术
  13. 名人名言页面的效果图HTML,名人名言书签制作方法图片
  14. 阿里TPP图化框架技术实践 — 打造算法在线服务领域极致开发体验与性能
  15. Blender学习笔记-印花(decal)贴图
  16. 在VSCode终端中安装Gulp包出现 * package is looking for funding run `npm fund..和无法加载文件,在此系统上禁止运行脚本的错误提示一步到位解决
  17. 美妆电商跌宕十年,跨境模式能否让其重新崛起?
  18. manifest原理和用途
  19. Android开源项目合集
  20. plink质控及转换文件、admixture软件学习记录

热门文章

  1. Android6.0之AMS启动app中篇之创建app进程
  2. 【AllenNLP入门教程】: 1、基于Allennlp2.4版本的文本分类
  3. 5mm方格本打印模板_自制方格本模板
  4. 《Bible》各版本
  5. 不知道如何做好精益生产管理?可能是你的企业还没有进行工时分析
  6. 直方图规定化计算过程
  7. 黑科技之中文计算机语言,双语 - 小巧玲珑的计算机语言 - Red
  8. CronTrigger 示例 1
  9. 计算机中用于表示储存,计算机中用来表示存储器容量的基本单位是
  10. 51单片机下载完程序后不亮_单片机实用工具大全,超级赞,工程师必备!