Moco是一个简单搭建模拟服务器的程序库/工具,这个基于Java开发的开源项目已经在Github上获得了不少的关注。该项目的简介是这样描述自己的:
Moco是一个简单搭建stub的框架,主要用于测试和集成。这个框架的开发灵感来自Mock框架,如Mockito和Playframework。
为什么要开发这个框架?
集成,尤其是基于HTTP协议的集成——web service,REST等,在我们的项目开发中被广泛应用。
以前,我们每次都要往Jetty或Tomcat等应用服务器上部署一个新的WAR。大家都知道,开发部署一个WAR的过程是很枯燥的,即使在嵌入式服务器上也是如此。而且,每次我们做一点改动,整个WAR都要重新组装。
Moco的出现,正是为了解决这些问题。开发团队只要根据自己的需要进行相应的配置,就会很方便得到一个模拟服务器。而且,由于Moco本身的灵活性,其用途已经不再局限于最初的集成测试,比如,Moco可以用于移动开发,模拟尚未开发的服务;Moco还可以用于前端开发,模拟一个完整的Web服务器,等等。
之前的《企业系统集成点测试策略》一文曾对Moco的基本用法和背后的一些理念进行了介绍。今天,我们邀请到了Moco框架的开发者郑晔,来跟我们分享一下Moco设计背后的一些思路。
InfoQ:郑晔你好,能否向大家做个简单的自我介绍、现在所从事的工作以及感兴趣的方向。
郑晔:大家好,我是郑晔,一个有十多年工作经验的程序员,现在在ThoughtWorks工作。这些年做过很多事情,包括开发和咨询,除此之外,做过演讲,也写过文章,翻译过书,也贡献过开源,愿意与人畅聊技术,也愿意分享自己的经验。个人一直热衷于探索各种程序设计语言在真实软件开发中所能发挥的威力,致力于探寻合理的软件开发方式。我的blog是梦想风暴,新浪微博是@dreamhead。
除了日常开发之外,近期的一项工作是,整理现代Java开发的一些知识,因为Java从诞生至今已经快20年,但现在很多Java程序员的代码编写方式和开发理念还停留在至少10年前。这些年,我涉猎过很多不同的程序设计语言和不同的项目,把其中学到的一些理念应用回Java开发,可以很大程度上提升Java原本笨拙的方面。我准备整理一下自己在这方面的理解分享给其他人。一些内容已经写成《你应该更新的Java知识》系列,发布在自己blog上。其中涉及到很多函数式编程的思想,比如,使用函数组合进行构建,这样的思想已经很好地体现在Moco的内部DSL设计上。
InfoQ:对于企业系统的集成测试,如果采取自行开发模拟服务器的方式,想必需要投入不少人力与时间成本,Moco在这方面的考量是什么,它是如何解决这个问题的?
郑晔:在企业级开发里,集成几乎是躲不开的。为了不耽误开发进度,开发团队通常都会开发一个模拟服务器,模拟要集成的服务。从十多年前我正式工作的第一个项目开始,我就在做这件事。当时刚开始工作,非常生猛,我直接从底层的Socket开始直接编写了一个Web服务器,支持我们所需的服务。后来,在各种不同的项目中,我经常遇到类似的情形,不断重复地构建着自己的模拟服务。构建服务的过程,非常繁琐,我也尝试过很多不同的框架做这件事,比如,Servlet、Ruby的Sinatra等。即便有框架支持,后期调整服务的过程也还是非常麻烦,比如Servlet需要重新部署,这是让人无法忍受的。
Moco正是为了解决这种问题而生的。目前,Moco直接支持的使用方式有两种,一种是Java API,另一种是独立服务器。Java API可以在我们在单元测试框架里,通过简单的配置,运行起一个模拟服务,而独立服务器的方式,则可以通过一个配置文件,配置模拟服务器。无论是Java API,还是独立服务器,相对于传统做法,Moco有两个非常明显的优势,一是提供DSL,可以非常直白地表现目的,二是启动速度非常快,无需漫长的等待。
InfoQ:相比于传统的Jetty容器结合Mock框架(如EasyMock等)方式来说,使用Moco有哪些优势?
郑晔:我不知道是否真的有人这么用过。Jetty一般是用在集成测试里,而Mock框架则是用在单元测试里,两种测试有着根本的不同。
我知道有类似于Moco的框架采用了Jetty作为底层的支持,但Moco没有走这条路。Jetty的目标是做一个Web Server,而不是一个模拟服务器,所以,以模拟服务器的需求看,它会有些重。另外一点,未来Moco可能不只做Http服务,也可能会做Socket服务。现在已经有些人向我提出了这方面的需求,也许会在后面的版本里实现。
至于Mock框架,其主要是在对象级别进行模拟,而Moco是在模拟服务,二者模拟目标的级别是不同的。
InfoQ:Moco的设计采用了内部DSL方式,请问其实现原理与好处有哪些?
郑晔:我一直对程序语言的表达性有着非常强的追求,表达性好的程序,理解起来也更容易,无论是现在的开发之时,还是后续的维护之日,都有极大的助益。我个人即便是选择使用程序库,也会倾向于选择表达性好的程序库。而DSL就是表达性在程序设计语言中最好的体现。我是Martin Fowler《领域特定语言(Domain Specific Language)》这本书中文版的译者之一。翻译DSL这本书的过程,加深了我对DSL的理解。
Moco的基本想法其实早就在我脑子里了,但我用了很长时间去找的一种把它写成内部DSL的方式。我从Mockito中受到启发,构想出描述服务端的方式,就是现在的请求/应答的基本结构,但怎么把它更好地运行起来一直困扰着我,直到有一天,我看到PlayFramework在测试方面的做法,豁然开朗,于是有了现在的running结构。
虽然有了思路,但写出一个好的内部DSL也不是一蹴而就的。一方面,设计一个可读的API并不是容易,选择哪些名词、动词、连接词着实费了我很多思量,往往一个API的设计要花很长时间来起名。另一方面,Java不支持集合字面量,所以,很多东西为了表达性只好采用函数,用其返回的对象表示一个基本概念,但Java的类型系统又有很多限制,比如,同样在请求和应答里都可以有文件的概念,但如果想用同一个语法表达文件这个概念,就必须有个中间层,分别适配请求和应答。
InfoQ:Moco还可以独立服务器的方式使用,这种方式与传统的Tomcat方式相比的优势有哪些、劣势有哪些、功能上有哪些差异性?
郑晔:无论是Tomcat,还是Jetty,它们首先是一个Servlet容器,是为了部署Servlet存在的,本身要支持Servlet的诸多特性,所以,对于模拟集成服务器这个需求而言,都是非常复杂的,Moco只是针对模拟服务这个特定的需求,就要简单很多。这样的差异有一个非常直观的体现:启动速度。我演示Moco时,很多人都惊讶于Moco飞快的启动速度。
这里面有一个很重要原因是,Moco最先实现的是Java API,初衷是为了把它用在单元测试里。如果启动速度慢,就没人会用了。所以,在开发时,我没有选择一个已有的容器,而是选择了一个Netty这个网络应用开发框架作为基础。
当然,Moco毕竟只是一个模拟服务器,它不像Servlet容器功能那么强大,可以做许多事情。说来有趣,在开发Moco的过程中,我曾冒出过这样的念头,让Moco支持函数,这样,用户就可以自定义一些简单的行为了。但后来,我打消了这个念头,一旦引入函数的概念,Moco就开始像一个真正的服务器,而不再是一个纯粹的模拟服务器,和我的初衷不一致了。也许有一天,我会写一个基于Moco理念的新Web框架,但它绝不是Moco。
其实,Moco独立服务器是个无心之作。我最初只是想实现Java API。后来看到一些类似的框架支持配置文件的形式,就顺手写了一个。没有想到,它会成为很多人使用Moco的一种重要方式,它也反过来影响到Moco核心API的设计,比如,现在Moco涉及的文件部分都是在运行时动态加载的,这就是为了满足独立服务器运行的需要,不必为了修改一个文件,反复重新启动。
InfoQ:在开发Moco时曾遇到过哪些棘手的问题?
郑晔:从技术上说,Moco不是一个很复杂的东西,实现难度不是很大。Moco最初的一个发布版,只有不到3000行代码。但这里面也有一些我的思考,我倾向于把事情做简单,因为把代码写多很容易,而写少却不容易。简单的东西维护和扩展都相对容易一些,才会走得更长远。无数的经验已经证明,复杂的东西只会为自身所累。我做咨询的时候,看到过很多动辄百万代码的项目,项目里的所有人都很挣扎,我们作为咨询师经常可以很轻松地把代码规模砍到原来的一半,因为里面重复代码太多。最近,我的一个同事为一个电信项目设计了一个新的模型,原来数百万行的代码一下子缩减到十万的规模,这是数量级的提升。很多软件最初的设计都是不错的,只是往往在最不起眼的写代码层面上搞砸了一切。我曾经在InfoQ上写过一个系列的《代码之丑》,就是我在各种项目中看到的代码没写好的地方。
Moco开发中如果说有一段棘手的日子,那是大约在开发两个月之后,所有的基础功能都实现了,我一度认为Moco的开发到此结束了。但后来,我把Moco介绍给更多的人,有人给我更多反馈,有人开始把Moco应用到实际的项目中。新的需求也就随之而来了,才有了后来熊节写的那篇文章《企业系统集成点测试策略》。而且,随着应用越来越广泛,各种新的用法也就涌现出来了,有人在移动开发中使用Moco模拟服务端,可以在没有真正服务端的情况下,进行移动端的开发;有人在前端开发中使用Moco模拟服务端,以便快速建立原型。所以,作为一个开源项目,真正生存下来的动力是有用人用,而不只是闭门造车。
编辑注:在今年的Duke's Choice Awards上,Moco框架被提名为最具创新力的Java项目之一。文中提到的《企业系统集成点测试策略》一文同时也在InfoQ英文站发布过,并在Twitter上得到了Martin Fowler的关注。