• 2014-02-01

    Moco 0.9.1发布

    Tag:Moco

    前版信息:Moco 0.9发布

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

    Moco是什么?

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

    变更

    按照版本号来说,这是一个小的修复版本,但实际的修改一点都不少。

    首先,这个版本增加了runner API,让我们可以自己在测试代码里控制Moco服务器的启停。最常见的做法是在集成测试的最初,启动一个服务器,结束之后关闭,下面是一个例子:

    @Before
    public void setup() {
       HttpServer server = httpserver(port());
       server.response("foo");
       runner = runner(server);
       runner.start();
    }

    @After
    public void tearDown() {
       runner.stop();
    }

    在配置API方面,增加了对proxy的批处理方法,我们可以一次性的代理一组URL,比如:

    server.proxy(from("/proxy").to("http://remoteUrl/target"));

    proxy还增加了一个playback的配置,它也是用来在本地存储远程服务器的内容,与failover不同的是,当本地内容可用时,它就不再访问远程服务器,换言之,它是本地优先的。

    server.request(by(uri("/proxy_playback")))
         .response(proxy("http://remoteUrl/target"), playback(path-to-local-playback)));

    这次的发布对模板也进行了改进,增加了对模板变量的支持,你可以根据自己的需要定制模板内容:

    server.request(by(uri("/template"))).response(template("${var}", "var", "TEMPLATE"));

    这次发布还增加了一个全局配置选项:Response。它的作用是,给所有的应答添加一个同样的内容,比如HTTP头。它主要用于独立运行模式,模拟很多类似的请求:

    [
       {
           "response" : {
               "headers" : {
                   "foo" : "bar"
               }
           },
           "include": "src/test/resources/settings/foo.json"
       }
    ]

    本次发布修正了Moco存在的一些问题,比如HTTP连接关闭,内容验证,服务器完全关闭等问题,让Moco的质量更上了一个台阶。本次发布还有一个重大的调整,采用Proguard将Standalone的JAR规模大幅度缩小,上一次发布Moco的Standalone包有8M多,而这次只有不到5M。

    更多的细节请参考ReleaseNotes

    感谢

    感谢崔鹏飞,协助发现修复了HTTP连接关闭的问题。

    感谢黄云坤,实现Proguard压缩Standalone JAR包。

    感谢Marcin Grzejszczak,协助发现并修复了服务器完全关闭的问题,并贡献了gmoco的脚本。

  • 当我在Moco的代码库里删除了关闭链接的代码时,我惊奇地发现Moco的一个测试失败了。怎么会失败呢?难道测试里面还有人需要强制关闭链接。

    带着种种疑问我又踏上了新一轮的代码跟踪之旅,这次的所有代码都在我自己的代码库里,一个简单的单元测试,无非是里面用到了Apache的HTTP Client。又是无数次的重试,我站到了一个InputStream的面前,这是在处理应答体时,要从HTTP应答流中读内容,然后,挂掉了。显然,当我强制关闭链接,读取就会返回,所以,测试可以通过。

    为什么会是这样?

    我不再简单粗暴地关闭链接,而是根据情况,如果客户端希望保持链接,我就保持,否则才会关闭。结果,测试依然是挂的。原因是这个测试请求保持链接,所以,服务器端不会关闭它的链接,所以,还会遇到相同的问题,它会挂住。

    到底什么样的行为才是正确的呢?我找到一本厚厚的《HTTP权威指南》,仔细读了一下保持链接的章节。我对HTTP如何处理链接有了一个相对完整的认识,但对于这个问题,我依然没有答案。

    其它测试也会有保持链接的需求,为什么就可以顺利通过,而只有这个测试会挂住。

    坐在电脑面前解决不了的问题,往往需要在路上解决。我突然意识到,通过者和不通过者似乎是有些许差别的,赶紧回到电脑前,打开日志,这个不通过的测试与其它测试最大的差别是,它只有应答头,没有应答体。不奇怪,因为这个测试就是测试能够返回正确的HTTP头。

    我似乎明白了,当客户端要求保持链接时,它还是需要从链接中读取数据的,但应该读取多少,谁能告诉它这个答案呢?如果没有额外的提示,似乎就只能挂住。答案就在Content-Length上,有了这个HTTP头,客户端就知道该读多少了。

    想通这一点,代码就好改了。在保持链接的情况下,如果没有Content-Length,就设置相应的Content-Length。

    果然测试一遍通过,又把之前同事的测试用例拿了过来,重新跑了一遍,一切正常。放到他们的工程里面测试,也是顺利通过。

    从发现一个不稳定的问题,到最终比较完整地修复了它,前前后后两个星期。如果没有我那个执着的同事,可能我也不会严肃地对待这个问题;没有仔细地解决这个问题,也不会对HTTP协议处理有个新的认识。非常感谢我这个执着的同事,他叫崔鹏飞,下一次Moco发布公告中,我可要着力感谢一下他。

    一个同事跑到我面前,我在使用Moco时发现一个问题,它有时会挂住。你用的是不是CXF,我问他。是啊!他回答。我笑了,因为我刚刚修好了这个问题。

  • 去参加QCon的路上,一个同事给我打了个电话,他在使用Moco的过程中,遇到了一个奇怪的问题:测试会时不时的挂住。在不明就里的情况下,我给出了一些常规性的建议,换个新版本试试,要不,能不能把情况简化到最简单的情形,发给我试试。

    几天后,我从QCon回来,那个同事愁眉苦脸地找到我:换了新版本,问题依然,更奇怪的是,当试图用最简单的情形重现时,现象居然就消失了。用抓包工具试试吧,看看底层发送的消息是不是对的。我又想到了一个常规的解决方案。他同意了,尝试了,又失败了,它在中间加了个代理,结果现象又消失了。两边发送的消息从HTTP报文的内容上,看不出什么端倪。

    我这个同事在办公室里是出了名的执着,几天之后,他又跑了过来,我已经写了个测试,已经能够稳定的重现这个现象了。我们一起坐下来,用单步跟踪的方式追着代码。经过无数次的运行,我们惊奇地发现,挂住的地方根本没有走到Moco里面,当第一次访问Moco服务器成功之后,离开Moco代码,在Netty框架里,试图select下一个要处理的请求时,就挂住了。而且很有趣的现象是,如果两次访问Moco服务器时间间隔较长,比如5秒,万事大吉,如果时间很短,基本上就会挂住。我们甚至开始怀疑是不是Netty本身出了问题。

    第二天,这个同事又取得了新的进展,他已经把这个测试简化到完全不依赖于他们的工程。这样,我们就可以在别的机器上进行调试,而且简化到最简单的情形之后,我们只关注最重要的东西。这次现象是相同的,挂住,但跟进去的结果却截然不同,这回出问题的地方是客户端,当发送了第一个请求之后,试图进行第二次发送时,请求就挂住了。

    我能怎么办呢?我又仔细品味了阅读了一下Moco的代码,突然注意到里面有这样一句:

      future.addListener(ChannelFutureListener.CLOSE);

    这句话的意思是,当请求处理完成之后,关闭链接。似乎灵光闪现,我查看了一下客户端发起的请求,在HTTP头里面有这样一句:

      Connection: keep-alive

    这显然是HTTP协议里面,用来请求服务器端保持链接的一种方式。如果你不知道的话,在一些HTTP协议的实现中,有时为了节省链接建立的资源,会复用之前已经建立好的链接,当然,前提条件是,服务器和客户端都支持。

    显然,Moco是不支持的。但客户端又有这种期望,所以,挂住了。简单地删除了负责关闭的代码,代码顺畅地通过了测试。这里面有两个问题,Moco服务器端支持是一方面,客户端实现是另一方面,换句话,当服务器端不支持长链接时,客户端应该做相应的处理,至少这个客户端没有做到,顺便说一下,这个库是CXF。

    这不是故事的结尾。

  • 2013-11-02

    Moco 0.9发布

    Tag:moco

    前版信息:Moco 0.8.1发布

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

    Moco是什么?

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

    变更

    本次发布包含两个重大的新特性,事件和验证。

    事件指的是,在某种情况下,触发相应的处理。目前支持的事件是“完成”事件,也就是说,当一个请求处理完成时,我们可以做相应的处理。

    比如,我们可以用如下代码在请求处理之后,发出另外一个请求。

    server.request(by(uri("/event"))).response("event").on(complete(get("http://another_site")));

    我们还可以声明这个请求是异步的,这样不会阻塞本次调用给客户端回应答。

    server.request(by(uri("/event"))).response("event").on(complete(async(post("http://another_site", "content"), latency(1000))));

    这种情况可以用于诸如OAuth认证之类的情形,在服务器接到一个请求之后,发起另外一个请求。

    它也支持相应的JSON配置:

    {
    "request": {
       "uri" : "/event"
    },

    "response": {
       "text": "event"
    },
    "on": {
       "complete": {
           "async" : "true",
           "latency" : 1000,
           "post" : {
               "url" : "http://another_site",
               "content": "content"
           }
       }
    }
    }

    验证,是一个用于测试代码中的特性,类似于Mock框架中的verify功能,对请求进行验证。下面是一个例子。

    RequestHit hit = requestHit();
    final HttpServer server = httpserver(12306, hit);
    server.get(by(uri("/foo"))).response("bar");

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

    hit.verify(by(uri("/foo")), times(1));

    这里的verify的第一个参数是一个matcher,也就是说,我们可以写任意的条件组合,甚至与设置服务器的完全不同。

    此外,我们还支持未预期请求的验证,如下:

    hit.verify(unexpected(), never());

    目前支持的验证条件包括:

    • never: 没有收到这样的请求。
    • time: 准确匹配收到这些次数的请求。
    • atLeast:至少收到这些次数的请求。
    • atMost:至多收到这些次数的请求。

    此外,还有一些小特性的变更:

    • 缺省情况下,关闭了使用API方式的日志。
    • seq API支持了ResponseHandler接口。
    • 为PUT和DELETE方法增加了快捷方式。

    在内部实现细节上也有一些微调:

    • 如果不提供端口,服务器会自行选择可用端口。
    • 在Failover的实现里,“statusCode”改成了“status”。如果因此造成了测试失败,可以手工修改Failover文件。

    感谢

    感谢Paul Hammant,为Moco提供了大量的反馈,并激发了验证特性实现的灵感。

  • 2013-10-18

    Moco,一岁了!

    Tag:Moco

    一年前的今天,我在github开启了一个新的小项目,为的是解决自己在日常开发中遇到的集成问题,它叫Moco

    一年后的今天,Moco一岁了!

    下面是一些Moco在这一年里的成果:

    • 发布了3个版本,从2013年5月1日发布0.7开始。
    • 有了524次提交,10个贡献者,42个Issue。
    • 在github上有了199个star。
    • 从2013年2月17日开始,迄今已经有连续243天的提交。
    • 分别有人写了Maven和Gradle插件
    • 有人给Moco写了Scala绑定

    更荣幸的是,在这一年里,Moco

    • 夺得了ThoughtWorks中国区第二届技术大赛最具技术创新奖。
    • 荣获2013年Duke选择奖。

    这一年里,Moco得到了更多的关注:

    • 越来越多的人把Moco用在了自己的项目上,于是,我得到了更多的反馈。
    • 越来越多的人开始关注Moco,许多网站报道了Moco,它还登上《Java Magazine》。

    启动Moco之时,我没想过这个项目可以走多长时间。一年后,Moco路越走越宽。

    Moco,继续前进!