写点什么

利用 Ruby 简化你的 Java 测试(进阶篇)

  • 2008-09-22
  • 本文字数:4354 字

    阅读完需:约 14 分钟

——Productive Java with Ruby 系列文章(二)

本文是 Productive Java with Ruby 系列文章的第二篇,通过上一篇的介绍,我想大家对如何利用Ruby 进行单元测试有了一个基本的了解,从这里开始,我将和大家一起讨论一些利用Ruby 进行单元测试时的高级话题。

通常,新技术的引入只能降低解决问题的难度,而不是消除问题本身!

在“依赖”的原始丛林中挣扎…

通过Ruby 我们可以更高效的处理数据准备的问题,但是真实的世界并不那么简单!随着测试的深入,我们会越发的感觉一不小心就挣扎在“依赖”的原始丛林中!有时候似乎需要加入无数的jar 包,初始化所有的组件,配置完一切的数据库、服务器及网络的关系,才能开始一小段简单的测试。更痛苦的是这一切是如此的脆弱,仅仅是某人在数据库中多加了一条数据或者更改了一部分环境配置,你苦心构建的所有测试就全部罢工了!多少次,你仰天长叹:“神啊!救救我吧…”。可神在那里呢?

Mock

单元测试之所以有效,是因为我们遵从了快速反馈,小步快跑的原则!一次只测试一件事情!而大量依赖的解决工作明显让单元测试偏离的原本的目标,也让人觉得不舒服。Mock 技术就能让我们有效摆脱在丛林中的噩梦。我们知道,在计算机的世界里,同样的输入一定能得到对应的输出,否则就是异常情况了。Mock 技术本质上是通过拦截并替换指定方法的返回值摆脱对程序实现的依赖。对于 1+1 这样的输入条件进行计算,Mock 技术直接拦截原方法,替换该计算方法的返回值为 2,不关心这个算法到底是通过网络得到的,还是通过本地计算得到的。这样就和具体实现解藕了。

在对 Java 进行单元测试的时候,通常会对某个具体类或某个接口产生依赖,要解藕就需要能够对具体类或接口进行 Mock。幸好这些在 JRuby 中都非常的简单,由于 JtestR 自动为我们引入了 mocha 这个 Mock 框架,让我们可以更简单的开始工作。先看一个针对 HashMap 的 Mock 测试吧:

map = mock(HashMap)           #=> mock java.util.HashMap 类,如果是接口可以直接 new 出来,例如 Map.new<br></br> map.expects(:size).returns(5) #=> 模拟并期望调用 size 方法时返回 5<br></br> assert_equal 5, map.size        #=> 断言,和 JUnit 断言非常相似 EasyMock 是个流行的开源 Java Mock 测试框架,在它的官方网站的文档中刚好有如何利用Mock 进行测试的示例,为了方便说明,我将直接引用这个示例,并用JRuby 实现基于Mock 的测试。首先我们有一个接口:

// 协作者接口,用以跟踪协作文档的相关状态 <br></br>public interface Collaborator {<br></br> void documentAdded(String title); // 当新增文档时触发 <br></br> void documentChanged(String title); // 当文档改变时触发 <br></br> void documentRemoved(String title); // 当文档被删除时触发 <br></br> byte voteForRemoval(String title); // 当文档被共享,并进行删除操作是,执行投票的动作 <br></br> byte[] voteForRemovals(String[] title); // 同上,不过可以同时投票多个文档 <br></br>}在这个示例中,还有一个ClassUnderTest类实现了管理协作文档的相关逻辑,简化示例代码如下:

public class ClassUnderTest {<br></br> // ... <br></br> public void addListener(Collaborator listener) {<br></br> // 增加协作者 <br></br> }<br></br> public void addDocument(String title, byte[] document) { <br></br> // ... <br></br> }<br></br> public boolean removeDocument(String title) {<br></br> // ... <br></br> }<br></br> public boolean removeDocuments(String[] titles) {<br></br> // ... <br></br> }<br></br>}到这里开始,我们就可以开始利用 JRuby 进行测试了。上一篇中我介绍了Ruby 的测试框架,不过这次,我们学习一个新的测试框架 dust ,它可以让你以更简洁的方式书写测试:

import "org.easymock.samples.ClassUnderTest"<br></br> import "org.easymock.samples.Collaborator"<br></br> unit_tests do<br></br>     cut = ClassUnderTest.new<br></br>     mock = Collaborator.new #=> mock 一个接口只需直接 new 出来即可 <br></br>     cut.addListener(mock)<br></br>#测试方法以 test 开始,后面跟一段具有描述性的字符串,然后在 block 中完成测试逻辑 <br></br>     test "001 remove none existing document" do<br></br>         cut.removeDocument("Does not exist")<br></br>     end<br></br> end将上述代码拷贝至src/test/ruby下,运行mvn test命令,OK,通过了相关测试。非常简单吧! dust 甚至让我们不用声明任何类就可以开始工作了,处处都体现着 ruby 简单、高效的理念!

加速

跑过几次单元测试后,大家一定会发现测试代码是很容易书写,但是跑测试的时间似乎有点长!难道 JRuby 的性能这么差?其实整个测试过程中启动 JRuby 花费了很多时间,JtestR 框架也考虑的很周到,只需要启动一个本地的测试服务器就可以大大加快测试执行的速度,在 shell 中执行mvn jtestr:server即可。再跑一次单元测试,速度大大增加了吧!

上面的代码只测试了删除一个不存在的文档,逻辑太过简单,不能说明任何问题,我们继续后面的测试,新增一个文档:

test "002 add document" do<br></br>         mock.expects(:documentAdded).with("New Document") #=> 我们期待 documentAdded 被执行,并且 title 的值为“New Document”<br></br>         <br></br>         cut.addDocument("New Document", [])<br></br>     end 运行测试,居然出错了,TypeError: for method addDocument expected [java.lang.String, [B]; got: [java.lang.String,org.jruby.RubyArray,原来错在cut.addDocument("New Document", [])的方法中我简单传入了[],这是一个 Ruby 数组对象,将这段代码改成:

cut.addDocument("New Document", [].to_java(:byte))重新运行测试,OK,全部通过。在 JRuby 中进行测试时调用 Java 对象的方法要注意将 Ruby 对象转换成 Java 对象。我们对比一下 JUnit 的代码

@Test<br></br> public void addDocument() {<br></br>     mock.documentAdded("New Document");<br></br>     replay(mock);<br></br>     classUnderTest.addDocument("New Document", new byte[0]);<br></br>     verify(mock);<br></br> }Ruby 代码还是稍稍比 Java 代码简洁一些,虽然优势不明显。我们继续完成后续的测试,增加并改变一个文档:

test "003 add and change document" do<br></br>     mock.expects(:documentAdded).with("Document")<br></br>     #在 ClassUnderTest 实现逻辑中,后续增加的同名文档属于修改操作,所以 documentChanged 事件被触发了三次 <br></br>     mock.expects(:documentChanged).with("Document").times(3)  #=> DSL here<p>     cut.addDocument("Document", [].to_java(:byte))</p><br></br>     cut.addDocument("Document", [].to_java(:byte))<br></br>     cut.addDocument("Document", [].to_java(:byte))<br></br>     cut.addDocument("Document", [].to_java(:byte))<br></br> end运行测试,全部通过!请大家注意mock.expects(..).with(..).times(3)这行代码,代码本身似乎就在说我期望这个对象的 XXX 方法被调用,参数是 xx,并且一共被调用了 3 次。书写简洁,阅读也非常的语义化!这就是我们所说的 DSL(Domain Specific Language), mocha 就是 Ruby 在 Mock 测试方面的领域化语言!它支持的语义非常的丰富,包括:

<span>at_least</span>   <span>at_least_once</span>   <span>at_most</span>   <span>at_most_once</span>   <span>in_sequence</span>   <span>never</span>   <span>once</span>   <span>raises</span>   <span>returns</span>   <span>then</span>   <span>times</span>   <span>when</span> 等等。DSL 的应用是 Ruby 的一大特点,它甚至能让我们写出连客户都能很容易看懂的测试代码。这在敏捷实践中,与用户讨论接收测试时就显得非常有用及必要!我们也同样对比一下 JUnit 和 EasyMock 的实现:p @Test<br></br> public void addAndChangeDocument() {<br></br>    mock.documentAdded("Document");<br></br>    mock.documentChanged("Document");<br></br>    expectLastCall().times(3);<br></br>    replay(mock);<br></br>         <br></br>     classUnderTest.addDocument("Document", new byte[0]);<br></br>     classUnderTest.addDocument("Document", new byte[0]);<br></br>     classUnderTest.addDocument("Document", new byte[0]);<br></br>     classUnderTest.addDocument("Document", new byte[0]);<br></br>     verify(mock);<br></br> }EasyMock 属于非常正常的 API 调用,没有太多 DSL 的概念,在这方面 JMock 相对来说要好一些,不过和 Ruby 相比,表达相同的语义,还是更繁琐一些。我们继续完成最后一段测试代码,删除及投票:

test "004 vote for removel" do<br></br>     mock.expects(:voteForRemoval).with("Document").returns(42)<br></br>     mock.expects(:documentRemoved).with("Document")<br></br>     assert_equal true, cut.removeDocument("Document")<br></br> end看到这里,细心的同学一定会发现有些奇怪,并没有先增加一个 Tilte 是 Document 呀?是的,这个是 Ruby 的单元测试和 Java 机制不一样的地方,JUnit 中,每个方法是在线程中执行的,不保证被执行的先后顺序,而 Ruby 的单元测试是简单反射,按字母排序后执行的,所以只有一个上下文环境。我特意在每个方法的描述前加了个数字序列,以保证按这个数字的大小顺序执行!

好了,到这里,对利用 Ruby 进行 Mock 测试介绍基本完成!剩余的 EasyMock 的示例测试留给大家自己完成吧!

总结

引入 Ruby 进行 Mock 测试可以有效简化单元测试时对各种环境的依赖,但是 Mock 也有 Mock 自己的问题,例如,它需要你对被测试类的内部细节有一定的了解,毕竟利用 Mock 技术进行测试属于白盒测试。当被测试类的内部实现有所改变而外部接口未发生变化时,原本不该出错的测试方法依旧有被打破的风险。还是回到开篇的那句话:通常,新技术的引入只能降低解决问题的难度,而不是消除问题本身!

相关阅读: Productive Java with Ruby 系列文章(一):利用 Ruby 简化你的 Java 测试


作者介绍:殷安平,现任阿里软件研究院平台二部架构师,工作 6 年以来一直从事 Java 开发,爱好广泛,长期关注敏捷开发。对动态语言有了强烈的兴趣,致力于将动态语言带入实际工作中!工作之余喜欢摄影和读书。个人 RSS 聚合: http://friendfeed.com/yapex 。联系方式:anping.yin AT alibaba-inc.com。

志愿参与 InfoQ 中文站内容建设,请邮件至 editors@cn.infoq.com 。也欢迎大家到 InfoQ 中文站用户讨论组参与我们的线上讨论。

2008-09-22 02:322333

评论

发布
暂无评论
发现更多内容

移动开发平台真的能提升App开发效率吗?

Onegun

移动应用开发 移动端开发

一种前端无源码定制化开发能力专利解读

元年技术洞察

低代码 数字化转型 无源码 元年方舟

太狠了,Spring全家桶笔记,一站式通关全攻略,已入职某厂涨薪18K

程序知音

Java spring ssm java架构 后端技术

MixMIM 创建混合图像,提出新型掩码方案

Zilliz

计算机视觉

从零到一,臻于至善|网易邮箱基于StarRocks 开发大数据平台的实践

StarRocks

数据库

架构实战 5 -微博评论高性能高可用计算架构

架构实战营 「架构实战营」

老马闲评数字化【2】您的企业是否应该急于数字化转型?

行云创新

云原生 数字化转型 企业数字化 老马闲评数字化 行云创新

行云洞见|为什么说云端IDE代表未来趋势?

行云创新

程序员 云原生 CloudIDE 集成开发环境 云端IDE

由浅入深,聊聊 LeackCanary 的那些事

Petterp

android 内存泄漏 LeakCanary

除 svn、Testlink 外,还有哪些不错的测试用例管理工具?

爱吃小舅的鱼

项目管理 管理工具 测试用例管理平台

软件测试/测试开发 | app自动化测试(Android)--高级定位技巧

测试人

软件测试 自动化测试 测试开发 appium

年度技术盘点:水稻、韦伯、脑机接口、AI预测及创作、快速充电

B Impact

2022年中国跨境支付行业年度专题分析

易观分析

金融 跨境支付 市场

混合式APP开发框架

力软低代码开发平台

2022年12月视频行业用户洞察:世界杯后半程看球热度不减,优质IP创新与开发助力用户留存

易观分析

音视频 视频

老马闲评数字化【3】业务说了算还是技术说了算?

行云创新

云原生 数字化转型 企业数字化 老马闲评数字化 行云创新

带你熟悉3种AQS的线程并发工具的用法

华为云开发者联盟

后端 开发 华为云 企业号 1 月 PK 榜 华为云开发者联盟

尚硅谷Spring6发布视频教程

小谷哥

阿里、腾讯、蚂蚁金服Java技术面试及经验总结(文末分享答案)

程序知音

Java java面试 后端技术 春招 八股文

C4D和3dmax有什么区别?

Finovy Cloud

3DMAX C4D

网易游戏实时 HTAP 计费风控平台建设

Apache Flink

大数据 flink 实时计算

如何集中式管理多个客户端节点传输任务-镭速

镭速

国家先进计算产业创新(宜昌)中心来了!

SENSORO

人工智能 大数据

2022评分最高十大信创解决方案出炉!

饿鱼

Domino的数据迁移方案

饿鱼

软件测试/测试开发 | app自动化测试(Android)--元素定位方式与隐式等待

测试人

软件测试 自动化测试 测试开发 appium

Github霸榜!竟是阿里技术官的微服务分布式项目实战笔记总结

程序知音

Java 分布式 微服务 java架构 后端技术

各大互联网公司面经分享:Java全栈知识+1500道大厂面试真题

程序知音

Java java面试 java架构 大厂面试题 八股文

模块五计算架构模式

程序员小张

「架构实战营」

软件测试/测试开发 | app自动化测试(Android)--App 控件定位

测试人

软件测试 自动化测试 测试开发 appium

推荐几款实用的移动开发平台

FinFish

移动开发 开发平台 移动开发平台 移动端开发 小程序技术

利用Ruby简化你的Java测试(进阶篇)_Java_殷安平_InfoQ精选文章