阅读更多

0顶
0踩

企业架构



4月26日,为期两天的开发者盛会Gopher China完美毕幕。在此次会议上,开发者们除了见到Go语言创始人Robert Griesemer,穿上了神气的“中国版土拨鼠T-shirt”外,还收获到了满满的技术干货。作为Go语言在国内的践行者,七牛云CEO许式伟在Gopher China上为开发者们分享了七牛如何做HTTP服务测试的经验。

基于HTTP协议来提供服务的好处是显然的。除了HTTP服务有很多现成的客户端、服务端框架可以直接使用外,在HTTP服务的调试、测试等工程领域都有现成的相关工具支撑。七牛大量的服务都基于HTTP,所以需要思考如何更有效地进行HTTP服务的测试。

七牛早期HTTP服务的测试方法是先写好服务端,然后写一个客户端SDK,再基于这个客户端SDK写测试案例。这种方法多多少少会遇到一些问题。首先,客户端SDK的修改可能会导致测试案例编不过。其次,客户端SDK通常是使用方友好,而不是测试方友好。服务端开发过程和客户端SDK耦合容易过早地陷入“客户端SDK如何抽象更合理”的细节,而不能专注于测试服务逻辑本身。我的核心诉求是服务端开发过程和客户端开发过程解耦,因为网络协议定好了以后,整个系统原则上就可以编写测试案例,而不用等客户端SDK的完成。

不写客户端SDK而直接做HTTP测试,一个直观的思路是直接基于http.Client类来写测试案例。这种方式的问题是代码比较冗长,而且它的业务逻辑表达不直观,很难一眼看出这句话想干什么。虽然可以写一些辅助函数来改观,但是会逐渐有写测试专用SDK的倾向。这种写法看起来也不是很可取,毕竟为测试写一个专门的SDK,看起来成本有些高了。

七牛当前的做法是引入一种httptestDSL文法。这是七牛为测试写的领域专用语言。这个语言的文法大概在2012年就已经被加入到七牛的代码库,后来有个同事根据这个DSL文法写了第一版本qiniutest程序。在决定推广用这个DSL来进行测试的过程中,我们对DSL不断地进行了调整和加强。虽然总体思路没有变化,但最终定稿的DSL与最初版本有较大的差异。目前来说,我已经可以十分确定地说,这个DSL可以满足90%以上的测试需求。现在所有新写的模块全部基于这套测试案例进行测试,它被推荐做为七牛内部的首选测试方案。



上图是这套DSL的“hello world”程序。执行预期:下载www.qiniu.com首页,要求返回的HTTP状态码为200。如果返回非200,测试失败;否则则测试通过,打印返回包的正文内容(resp.body变量)。打印resp.body通常是调试需要,而不是测试需要。自动化测试是不需要去打印什么的。



我们再看该DSL的一个“quick start(快速入门)”样例。以#开始的内容是程序的注释部分。这里有一个很长很长的注释,描述了一个基本的HTTP请求测试的构成。后面我们会对这部分内容进行详细展开,这里暂时跳过。这段代码的第一句话是定义了一个auth别名叫qiniutest,这只是为了让后面具体的HTTP请求中授权语句更简短。紧接着是发起一个POST请求,创建一个内容为 {"a": "value1","b": 1}的对象,并将返回的对象id赋值给一个名为id1的变量。后面我们会详细解释这个赋值过程是如何进行的。接着我们发起一个获取对象内容的GET请求,需要注意的是GET的URL中引用了id1的值,这意味着我们不是要取别的对象的内容,而是取刚刚创建成功的对象的内容,并且我们期望返回的对象内容和刚才POST上去的一样,也是{"a":"value1", "b": 1}。这就是一个最基础的HTTP测试,它创建一个对象,确认创建成功,并且尝试去取回这个对象,确认内容与预期的一致。这里上下两个请求是通过一个变量来关联的。

对这套DSL文法有了一个大概的印象后,我们开始来解剖它。先来看看它的语法结构。首先这套httptest DSL基于命令行文法:
command switch1 switch2 …arg1 arg2…

整个命令行先是一个命令,然后紧接着是一个个开关(可选),最后是一个个的命令参数。和大家熟悉的命令行比如LinuxShell一样,它也会有一些参数需要转义,如果参数包含空格或其他特殊字符,则可以用'\'转义,如'\ '表示' ','\t'表示TAB等。另外,我们也支持用 '…' 或者 "…" 去引用一个比较复杂的文本作为参数,比如json格式的多行文本。同 LinuxShell类似,'…' 里面的内容没有转义,'\' 就是 '\','\t' 就是 '\t',而不是 TAB。而 "…" 则支持转义。

和Linux Shell 不同的是,我们的httptest DSL虽然基于命令行文法,但是它的每一个参数都是有类型的,也就是说这个语言有类型系统,而不像Linux Shell只有字符串。我们的httptest DSL支持切仅支持所有json支持的数据类型,包括:
  • string (如:"a"、application/json),在不引起歧义的情况下,可以省略双引号
  • number (如:3.14159)
  • boolean (如:true、false)
  • array (如:["a", 200, {"b": 2}])
  • dictionary/object (如:{"a": 1, "b": 2})

另外,我们的httptestDSL也有子命令的概念,它相当于一个函数,可以返回任意类型的数据。比如 `qiniu f2weae23e6c9f jg35fae526kbce` 返回一个 auth object,这用字符串无法表达。

理解了httptestDSL后,我们来看看如何表达一个http请求。它的基本形式如下:
req <http-method> <url>
header <key1> <val11> <val12>
header <key2> <val21> <val22>
auth <authorization>
body <content-type> <body-data>

第一句是req指令,带两个参数: 一个是http method,即http请求的方法,另一个是要请求的URL。接着是一个个自定义的header(可选),每个header指令后面跟一个key(键)和一个或多个value(值)。然后是一个可选的auth指令,用来指示这个请求的授权方式。如果没有auth语句,那么这个http请求是匿名的,否则这就是一个带授权的请求。最后一句是body指令,顾名思义它用来指定http请求的正文。body指令也有两个参数,一个是content-type(内容格式),另一个是body-data(请求正文)。
这样说比较抽象,我们看下实际的例子:
无授权的GET请求:
req GET http://www.qiniu.com/
带授权的Post请求:
req POST http://foo.com/objects
auth 'qiniu f2weae23e6c9fjg35fae526kbce'
body application/json '{"a":"hello1", "b":2}'

也可以简写成:
  • 无授权的GET请求:

get http://www.qiniu.com/
  • 带授权的Post请求:

post http://foo.com/objects
auth 'qiniu f2weae23e6c9fjg35fae526kbce'
json '{"a": "hello1","b":2}'

发起了http请求后,我们就可以收到http返回包并匹配。http返回包匹配的基本形式如下:
ret <expected-status-code>
header <key1> <expected-val11><expected-val12>
header <key2> <expected-val21><expected-val22>
body <expected-content-type><expected-body-data>

我们先看ret指令。实际上,请求发出去的时间是在ret指令执行的时候。前面req、header、auth、body指令仅仅表达了http请求,但是如果没有调用ret指令,那么系统什么也没有发生。

ret指令可以不带参数。不带参数的ret指令,其含义是发起http请求,并将返回的http返回包解析并存储到resp的变量中。而对于带参数的ret指令:
ret <expected-status-code>

它等价于:

ret
match <expected-status-code> $(resp.code)

这里我们引入了一个新的指令 —— match指令:



七牛所有http返回包匹配的匹配文法,都可以用这个match来表达:



所以本质上来说,我们只需要一个不带参数的ret,加上match指令,就可以搞定所有的返回包匹配过程。这也是我们为什么说match指令是这套DSL中最核心的概念的原因。

和其他自动化测试框架类似,这套DSL也提供了断言文法。它类似于CppUnit或JUnit之类的测试框架提供assertEqual。具体如下:
equal<expected> <source>
  • 与match不同,<expected>、<source>中都不允许出现未绑定的变量
  • 与match不同equal要求<expected>、<source>的值精确相等

equalSet<expected> <source>
  • SET是指集合
  • 与equal不同,equalSet要求<expected>、<source>都是array,并且对array的

元素进行排序后两者精确相等
  • equalSet的典型使用场景是list类的API,比如列出一个目录下的所有文件,你可能预期这个目录下有哪些文件,但是不能预期他们会以什么样的次序返回

以上介绍基本上就是这套DSL最核心的内容了。内容非常精简,但满足了绝大部分测试场景的需求。下面我们谈谈最后一个话题:测试环境的参数化。

为了让测试案例更加通用,我们需要对测试依赖的环境进行参数化。比如,为了让测试脚本能够同时用于stage环境和product环境,我们需要把服务的Host信息参数化。另外,为了方便测试脚本入口,我们通常还需要把用户名/密码、AK/SK(公私钥对)等敏感性信息参数化,避免直接硬编码到测试案例中。

为了把服务器的Host信息(也就是服务器的位置)参数化,我们引入了host指令。例如:
host foo.com 127.0.0.1:8888
get http://foo.com/objects/a325gea2kgfd
auth qiniutest
ret 200
json '{"a": "hello1","b": 2}'
这样,后文所有出现请求foo.com地方,都会把请求发送到127.0.0.1:8888这样一个服务器地址。要想让脚本测试另一个测试服务器,我们只需要调整host语句,将127.0.0.1:8888调整成其他即可。

除了服务器Host需要参数化外,其他常见的参数化需求是Username/Password、AK/SK(公私钥对)等。AK/SK这样的信息非常敏感,如果在测试脚本里面硬编码这些信息,将非常不方便测试脚本的入库。一个典型的测试环境参数化后的测试脚本样例如下:



其中,env指令用于取环境变量对应的值(返回值类型是string),envdecode指令则是先取得环境变量对应的值,然后对值进行json decode得到相应的object/dictionary。有了$(env)这个对象(object),就可以通过它获得各种测试环境参数,比如 $(env.FooHost)、$(env.AK)、$(env.SK) 等。

写好了测试脚本后,在执行测试脚本之前,我们需要先配置测试环境:
export QiniuTestEnv_stage='{
"FooHost":"192.168.1.10:8888",
"AK":"…",
"SK":"…"
}'

export QiniuTestEnv_product='{
"FooHost":"foo.com",
"AK":"…",
"SK":"…"
}'
这样我们就可以执行测试脚本了:
  • 测试 stage 环境:

QiniuTestEnv=stage qiniutest ./testfoo.qtf
  • 测试 product 环境:

QiniuTestEnv=product qiniutest ./testfoo.qtf

测试是软件质量保障至关重要的一环。一个好的测试工具对提高开发效率的作用巨大。如果能够让开发人员的开发时间从一小时减少到半小时,那么日积月累就会得到惊人的效果。七牛非常乐意去关注开发人员日常工作过程中的不爽和低效率,我们认为,任何开发效率提升相关的工作,其收益都是指数级的。这也是七牛团队所推崇的做事风格。

本文转自:七牛的博客
  • 大小: 34.9 KB
  • 大小: 47.8 KB
  • 大小: 150.2 KB
  • 大小: 107.1 KB
  • 大小: 110.2 KB
  • 大小: 92.2 KB
来自: 七牛的博客
0
0
评论 共 2 条 请登录后发表评论
2 楼 you_meng 2015-05-11 14:46
咳咳,软文有点软的太假了啊
1 楼 suneyejava 2015-05-10 17:28
这个测试套件对外开放吗,哪里有下载?

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 微软的正则表达式教程

    微软官方教程 正则表达式的早期起源 正则表达式的“祖先”可以一直上溯至对人类神经系统如何工作的早期研究。Warren McCulloch 和 Walter Pitts 这两位神经生理学家研究出一种数学方式来描述这些神经网络。 1956 年, 一位叫 Stephen Kleene 的美国数学家在 McCulloch 和 Pitts 早期工作的基础上,发表了一篇标题为“神经网事件的表示法”的论文,引入了正则表达式的概念。正则表达式就是用来描述他称为“正则集的代数”的表达式,因此采用“正则表达式”这个术语。 随后,发现可以将这一工作应用于使用Ken Thompson 的计算搜索算法的一些早期研究,Ken Thompson是Unix 的主要发明人。正则表达式的第一个实用应用程序就是 Unix 中的qed 编辑器。 如他们所说,剩下的就是众所周知的历史了。从那时起直至现在正则表达式都是基于文本的编辑器和搜索工具中的一个重要部分。

  • 微软正则表达式库的实现与应用

    本文还有配套的精品资源,点击获取 简介:正则表达式是一种用于文本处理的强大工具,在Windows环境下微软提供了相应的支持。本项目涉及的微软正则表达式库可能是一个内部或第三方开发的框架,以C++实现。 regexpr2.cpp 和 syntax2.cpp 文件可能包含核心匹配算法和语法解析处理,而 reimpl2.h 、 r...

  • 正则表达式匹配规则及示例

    正则表达式 - 匹配规则 基本模式匹配 一切从最基本的开始。模式,是正规表达式最基本的元素,它们是一组描述字符串特征的字符。模式可以很简单,由普通的字符串组成,也可以非常复杂,往往用特殊的字符表示一个范围内的字符、重复出现,或表示上下文。例如: ^once 这个模式包含一个特殊的字符^,表示该模式只匹配那些以once开头的字符串。例如该模式与字符串&quot;once upon a time&quot;匹配,...

  • 微软的正则表达式教程(一):正则表达式简介

    认识正则表达式 如果原来没有使用过正则表达式,那么可能对这个术语和概念会不太熟悉。不过,它们并不是您想象的那么新奇。 请回想一下在硬盘上是如何查找文件的。您肯定会使用 ? 和 * 字符来帮助查找您正寻找的文件。? 字符匹配文件名中的单个字符,而 * 则匹配一个或多个字符。一个如 data?.dat 的模式可以找到下述文件: data1.dat data2.dat datax.dat dataN

  • 微软的正则表达式教程(四):限定符和定位符

    限定符 有时候不知道要匹配多少字符。为了能适应这种不确定性,正则表达式支持限定符的概念。这些限定符可以指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。 下表给出了各种限定符及其含义的说明: 字符 描述 * 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。 * 等价于{0,}。 + 匹配前面的子表达式一次或多次。例如,zo+ 能匹配 "zo"

  • 微软的正则表达式教程(二)

    微软的正则表达式教程(二):正则表达式语法和优先权…正则表达式语法一个正则表达式就是由普通字符(例如字符 a 到 z)以及特殊字符(称为元字符)组成的文字模式。该模式描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。这里有一些可能会遇到的正则表达式示例:JScript VBScript 匹配 /^/[ /t]*$/ "

  • 正则表达式

    1.正则表达式如果原来没有使用过正则表达式,那么可能对这个术语和概念会不太熟悉。不过,它们并不是您想象的那么新奇。请回想一下在硬盘上是如何查找文件的。您肯定会使用 ? 和 * 字符来帮助查找您正寻找的文件。? 字符匹配文件名中的单个字符,而 * 则匹配一个或多个字符。一个如 data?.dat 的模式可以找到下述文件:data1.datdata2.datdatax.datd

  • 正则分组

    正则中分组的概念有时可以帮助我们很方便的把字符中的特定的或是符合我们需求的部分摘取出来。 比如我们需要把邮箱中的用户名取出来。 有个邮箱 Narcissus@163.com ,我们需要把这个邮箱中的用户名也就是 Narcissus 这个英文匹配出来。可以写个分组的正则: var reg = /([0-9a-zA-Z_.-]+)[@]([0-9A-Za-z_-]+)(([.][a-zA-Z]+){1...

  • 正则表达式分组模式

    正则表达式分组模式 捕获组 在正则表达式中,使用“()”进行分组,一对圆括号括起来的表达式就是一个分组。 捕获组就是匹配到括号中的内容 var reg = /(\?|&amp;amp;)name=[^&amp;amp;]*(&amp;amp;|$)/; 像这样一个正则表表达式,捕获组中的内容就是括号匹配到的 var _cuttent_url = www.baidu.com?name=4567&amp;amp;b=3456; ...

  • 正则表达式分组、引用和断言

    这几日看权威指南,对正则里的分组、引用和断言有了更深的理解,特地整理一下加深印象。 为了详细地解释,首先将权威指南第6版上相关描述的原文贴出来,重点用红色标识。 字符含义 (......) Grouping. Group items into a single unit that can be used with *, +, ?, | , and so on. Al...

  • 正则表达式高级用法(分组与捕获)

    正则表达式高级用法(分组与捕获) 分组的引入:     对于要重复单个字符,非常简单,直接在字符后卖弄加上限定符即可,例如 a+ 表示匹配1个或一个以上的a,a?表示匹配0个或1个a。这些限定符如下所示:   X ? X ,一次或一次也没有 X * X ,零次或多次 X + X ,一次或多次 ...

  • 正则表达式学习笔记 (转载)

    正则表达式学习笔记  正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串做替换或者从某个串中取出符合某个条件的子串等。  列目录时, dir *.txt或ls *.txt中的*.txt就不是一个正则表达式,因为这里*与正则式的*的含义是不同的。  为便于理解和记忆,先从一些概念入手,所有特殊字符或字符组合有一个总表

  • 正则表达式分组例子

    Oralce例子 SELECT REGEXP_REPLACE('20120202080259', '(.{4})(..)(..)(..)(..)', '\1-\2-\3 \4:\5:') "date" from dual;date ------------------- 2012-02-02 08:02:59SELECT REGEXP_REPLACE( 'A B C', '(.*) (.*) (.*

  • VBScript中的正则表达式语法详解

    ★●黑基论坛●★s Archiver ★●黑基论坛●★ » 软件开发技术 » VBScript中的正则表达式语法详解330466386 发表于 2009-2-10 19:34VBScript中的正则表达式语法详解在vbscript中功能最强大也最灵活的的要数正则表达式(RegExp对象)和类(class)了。    我也是在不久前才完全的懂得正则表达式的。----------

Global site tag (gtag.js) - Google Analytics