• 2013-08-01

    Moco 0.8.1发布

    Tag:moco

    前版信息:Moco 0.8发布

    我很高兴地宣布,Moco 0.8.1发布了。

    Moco是什么?

    Moco是一个可以轻松搭建测试服务器的框架/工具/程序库。

    变更

    这次是一个小的版本发布,没有特性上的任何变更。

    本次发布主要是修复了一个Proxy实现中的一个bug:如果客户端发起的请求中包含了Host这个Header,转发到某些服务器会造成这些服务器无法返回正确的结果。

    除此之外,一个重大的调整是把底层实现由Netty 3换成了Netty 4。

    Netty 3到Netty 4是一个非常大的版本升级,几乎所有API都做了调整。这样的升级带来了更流畅的API,更好的性能,更好的模块划分。

    感谢

    感谢李毅,发现并修复了Proxy实现中的bug。

  • 2013-07-21

    Moco 0.8发布

    Tag:moco

    前版信息:Moco 0.7发布

    我很高兴地宣布,Moco第二个正式版本0.8发布了。

    Moco是什么?

    Moco是一个可以轻松搭建测试服务器的框架/工具/程序库。

    特性变更

    本次发布有一个重大的新特性,支持工程配置文件(Global Settings)。

    我们可以通过它,将一个复杂的Moco配置文件分解成多个配置,然后,通过这个配置文件将它整合起来:

    [
       {
           "include" : "foo.json"
       },
       {
           "include" : "bar.json"
       }
    ]  
    (settings.json)

    这个配置文件还支持几个不同的选项,让我们在开发时有更灵活的变化:

    • 支持Context:我们可以把不同的服务,部署到不同的context路径下,实现由一个Moco服务器,模拟多个服务。
    • 支持File Root:我们可以配置文件根目录,这样一来,每个具体配置文件内,所有与文件相关的路径都可以写成相对路径。
    • 支持Environment:通过指定环境变量,同一套配置在不同的环境下,由不同的部分起作用,这样就可以开发代码无需任何调整,就可以得到不同的效果。

    本次还包含一些API的变化:

    • Proxy:可以将请求(包括请求的所有信息,比如内容、头灯)转发给另一个服务器,还可以通过配置failover文件,用以故障恢复,在远程服务器失败,无法返回时,从failover文件中提取请求内容进行返回。目前failover文件是JSON格式,可以手工编辑。
    • JSONPath:请求内容按照JSONPath进行匹配。
    • with:在API上增加with,用以进行Resource到ResponseHandler的适配。
    • 移除url:用Proxy代替。
    • 移除cache:用failover代替。
    • 移除content:用with代替。

    本次发布还有一个实验性的新特性:模板,用以根据请求定制应答。目前包含有

    • 请求版本:${req.version}
    • 请求方法:${req.method}
    • 查询参数:${req.queries['foo']}
    • 请求头部:${req.headers['foo']}
    • 请求内容:${req.content}

    这个特性还在进一步探索中,欢迎反馈各种问题。

    感谢

    感谢nezhazheng,为Moco贡献JSONPath实现。

  • 2013-05-01

    Moco 0.7发布

    Tag:moco

    我很高兴地宣布,Moco第一个正式版本0.7发布了。

    Moco是什么?

    Moco是一个可以轻松搭建测试服务器的框架/工具/程序库。

    Moco的使用场景

    • 我是一个企业级软件开发人员,每次面对集成就是我头疼开始的时候,漫长集成拉锯战拖延了我们的进度。幸好有了Moco,几行配置就可以模拟一个服务,我再也不需要看集成服务团队的脸色了。
    • 我是一个移动开发人员,老板催得紧,可服务器端开发进度慢,我空有一个漂亮的iphone应用,发挥不出作用。幸好有了Moco,很快就可以搭建出一个模拟服务,我再也不用把生命浪费在无效的等待上了。
    • 我是一个前端开发人员,做Inception的时候,客户总想看到一个完整的应用演示,可哪有时间开发后端服务啊!幸好有了Moco,几下就可以弄出一个模拟服务,我做的页面一下就有了生命力。

    使用Moco

    Moco目前支持多种使用方式,最基本的方式是两种:API独立运行

    如果你编写的是个Java应用(或是以其它JVM上的语言编写的应用),你可以采用直接调用API;我们也可以独立运行的方式,在配置文件中,配置一个属于自己的服务器。

    如果你是Mac或是Linux用户,你还可以选择Shell的方式运行。事实上,它是最简单的方式,它会自动帮助你找到最新版本的Moco。

    如果你在用Maven,那么Moco的Maven插件你一定会喜欢。作为一个新时代的自动化用户,Gradle插件也是不可或缺的。

    特性

    此次发布的特性包括

    • 对HTTP的支持
      • 内容(文本,文件)
      • URI
      • 查询参数
      • HTTP方法
      • HTTP版本
      • HTTP头
      • 状态码
      • 重定向
    • 对Web的支持
      • cookie
      • form
    • 对集成的支持
      • XML
      • XPath
      • JSON
      • 访问其它URL
    • 杂项
      • 延迟
      • 缓存
      • 目录挂载

    具体的使用方式,可以看这里

    感谢

    感谢刘宇峰熊节赵俊伟Garrett Heel,为Moco贡献代码。

    感谢郭晨,为Moco实现了一个favicon。

    感谢何海洋,贡献了Shell的支持。

    感谢Garrett Heel,实现了Maven插件。

    感谢银大伟,实现了Gradle插件。

  • Moco在程序库设计包括两个方面,如何设置服务器和如何让服务器运行起来。

    先说简单的,如何让服务器运行。最简单的选择是让用户自己启动服务器,然后,在测试结束之后关闭服务器。这么一来,每个测试都大概会是这样:

    public void shouldWork() {
     ... setup server ...
     try {
       server.start();
       ... your test here ...
      } finally {
       server.stop();
     }
    }

    作为一个框架,留给客户使用的接口一定简单,把实现细节封装在框架以内。如果每个测试都这么写,用户很快就会骂娘了。现在的Moco的做法是使用了running方法,把Server启停的细节封装了起来。

    running(server, new Runnable() {
     @Override
     public void run() throws Exception {
       assertThat(helper.get(root()), is("foo"));
     }
    });

    我知道你不喜欢匿名内部类,我也不喜欢,但是,这是Java的局限。等到Java 8驾临,相信一切会有所改观。

    设置服务器,也就是在什么样的请求,给什么样的应答。Moco有一个很关键的起点,那便是DSL,要知道,之前的一段时间我刚刚完成了《领域特定语言》的翻译。于是,API的可读性成了一件非常重要的事。

    说起来很简单,但是,要知道匹配请求的条件有很多,而且还可能任意组合。如果这些条件是彼此独立的(“或”),我可以用变参来解决,但有时,这些条件是相关的(“与”),那该怎么办呢?拜函数式编程思路所赐,我想到了函数组合的方式。

    我知道,Java没有一等公民的函数,但是,Java里有一等公民的对象,我们可以用对象模拟函数,事实上,这也是很多面向对象程序设计语言解决函数组合的一种方式,而这种对象称为函数对象,也叫functor。

    把需要的条件封装成一个个函数对象,然后通过几个简单的运算符就可以将它们组合起来。目前Moco里提供了and和or两个运算符用于组合。

    server.request(and(by(uri("/foo")), by(method("put")))).response("bar");
    server.request(or(by("foo"), by(uri("/foo")))).response("bar");

    随着面向对象思路的普及,函数组合的写法会越来越普遍的,它会成为程序员工具箱中的必备品。

    DSL是一个重要的考量,所以,这里没有直接new对象,而是用函数(比如uri、method)做了一层封装。或许你会说,那为了可读性,我可以把类名设计成一个可读的样子,但new一个对象的问题在哪呢?就在new上。一方面,new是为了创建一个对象,这是实现细节,与我们要表达的内容不在一个层次,另一方面,你会发现多个new会让这句话显得不连贯。这句话?是的,我们的写法就像是用一句话声明一个东西一样,而我们期望自己讲的内容有一定的连贯性,而这才是DSL。

    一旦设计成DSL,单个函数的文档也就失去了意义,更重要的是一个用法的文档。所以,在Moco里,我写了文档,却没有写JavaDoc。

    在Moco里,请求和应答里可能有同样的东西,比如file,而request和response的参数类型是不一样的。如果file能够返回不同类型是最好的,可惜在Java里面,我们不可能根据返回类型做重载。一种解决方案是为request和response分别设计一个函数,比如requestFile和responseFile,但显然,这种做法会把同样的逻辑分散到两个类实现里,而且这样需要共享的东西不只是file,还有其它一些东西。

    在计算机问题里,加上一个中间层永远是一个重要的解决方案。这也是by的目的所在。这样一来,这些共享的东西就可以做成一个抽象(Resource),对请求来说,只要有一个by,就可以适配成Matcher。

    在Moco设计中,还涉及到了一个框架设计中很重要的点:类型。Java是静态类型程序设计语言,利用好类型,就可以在编译期把错误报出来,而不会留到运行时,fail fast是很重要的一个程序设计实践。

    Moco目前支持一些Content Type检测。如果把这个Content Type放到Resource里面,你会发现不是所有Resource都需要这个,比如method,我们当然可以在Resource接口里支持Content Type,在不需要的地方抛出异常。

    在Moco里,我的做法是引入了一个ContentResource,支持Content Type,它继承于Resource,这样依赖,需要Content Type的接口(比如content),直接支持ContentResource,而其它部分继续支持Resource,这样,如果一不小心用错的话,编译就会报错。

    再有一点是关于Publish接口,在Moco的实现里有一个Setting,还有一个BaseSetting,如果观察实现,在设置Request会创建出一个BaseSetting,但其返回的接口是Setting。这么做的一个原因是,因为Setting出现在用户可以使用的接口上,而BaseSetting是留给内部实现的,它提供了一些用于内部实现的方法,而用户是不应该使用这些方法的,所以,将二者做了一个分离,这样一来,保证了用户不会误操作。同样处理的还有HttpServer和ActualHttpServer。

    小结一番,简化用户接口,设计DSL,利用好类型,区分Publish接口。

  • 时隔一年,我又参加了成都的Open Party。这次参加Open Party,我带的主题是Moco。

    Moco最近发展得不错。多谢gigix,已经有第一个实际的商业项目开始使用Moco,他不断给我提出新的需求,最近一段时间,我每天忙不停地给Moco写新代码。我也越来越觉得,Moco开始告别我一个人的自娱自乐阶段,应该与更多人分享。于是,我决定在社区活动里做一次Moco的分享。

    分享总是有益的,当你把自己的东西抛出去,就会有人把他的想法抛给你。这不,有人给Moco提出了新特性。

    一个特性是延迟,也就是让服务器等待一段时间返回,用以模拟服务器的慢速操作。这是第二次有人给我提出这个想法,我认为这个需求真的是有必要了。鉴于这个特性实现了起来很简单,我已经完成了编程工作,提交到代码库里了。

    另一个特性是支持Socket。现在Moco只支持HTTP,因为在我目前的工作里,这是优先级最高的。在这次活动之后,分别有两个人与我交流时都提到了Socket模拟的想法。在一些游戏项目里,对于性能的要求很高,Socket是一个比HTTP更好的选择。我已经把Socket列到了我的TODO列表里,只是目前而言,我还需要更多的需求以确定Socket模拟该如何表现。

    在分享之后,有人问了一些有趣的问题。

    有人关心Moco启动飞快的原因,底层用了哪个库。目前Moco底层用的Netty,JBoss的一个异步IO库,类似的库还有grizzlyMina。虽然Moco支持的HTTP,但我并没有选择Tomcat或Jetty,从Moco提供的接口可以看到,我实在是不需要Sevlet那层抽象。也是因为省去了一层抽象的原因,启动速度自然就是飞快了。Netty本身是一个更底层的库,未来支持Socket的话,也可以拿来就用。

    有人问是不是ThoughtWorks是不是给我专门的时间写开源项目。上班时间肯定主要是给客户写代码,那我的时间从何而来。关于这个问题,最好的回答是之前的一篇blog《聊聊早起》。现在每天在5点半左右起床,上班之前大约会有一个半到两个小时做一些自己要做的事情。每天坚持下来,就会是一笔很好的时间财富。我现在基本上也是把这个做法作为一种最佳实践推荐给身边的许多人,我知道,已经有人开始从中受益了。

    把自己的东西拿出来,别人会回馈给我们更多,这就是社区分享的好处。