mojito

mojito jar 包 下载 :  http://download.csdn.net/detail/richard_jason/9660916

mojito是一个简单而灵活的测试框架,最初开发它是为了解决OpenSearch前端测试中遇到的一些问题,但是随着mojito的开发,我们发现它可以被应用在更广泛的范围里。

mojito目前只是测试工具,而非测试服务。用户可以按照其规则,通过yaml文件定义测试工程和测试用例,并使用mojito来执行这些用例。而mojito的一个强大之处在于,它可以很方便的进行扩展。在mojito看来,任何测试用例都是由一组“测试行为(action)”组成的,除了先天内置的一些action外,用户可以直接在yaml文件里自定义新的action,来基于已有action进行扩展或组合。后面会有详细介绍。

在自定义action的基础上,mojito还支持优美的上下文系统。用户可以分别在工程(project)、用例(case)、行为(action)这几个方面进行递进的变量定义和传递。

在公司内部,有一些和mojito类似的测试框架,例如tiramisu和ATest,mojito不是为了替代这些框架,而是纯粹为了更好的适应OpenSearch的测试。已有框架在扩展性和易用性上存在一些问题,使得OpenSearch开发自己的mojito的成本在某种意义上低 于应用那些框架。同时,mojito也是搜索测试对Clojure语言应用在测试开发中的一个尝试,不得不说,到目前为止,收效符合预期。

名称来源:mojito原意是一种鸡尾酒 (link),来源于古巴,是大作家海明威的最爱。话说起这个名字的由来也属偶然。mojito的开发过程参考了很多tiramisu的思想和“外观”,于是在起名字的时候,就也想找一个甜点蛋糕的名字来命名,结果没找到什么合适的。此时刚好想起了夏天最爱喝的一种酒精饮料:mojito。青柠的酸爽配上薄荷的清凉,白朗姆的甘冽沉浸在苏打水的泡泡中,简直是夏日的绝佳消暑饮品。帝都找不到正宗的mojito卖,于是每年夏天,老婆都会给我自制上几次,即使是冬天,那味道也常常在脑海中回荡。给这个工具起这个名字,也是希望我们的测试功能能够爽酷起来吧。

安装

请从此处下载mojito的最新版本

下载后解压,并把解压后的文件夹添加到可执行目录即可。

使用方法

注意:请在使用前确保jre1.7已经被正确安装,且java命令在当前path路径内

运行项目:

mojito run -p "project-path"

获取帮助:

mojito -h

使用概述

如前文所说,mojito的核心理念是:所有的用例都由一系列“行为”组成,在mojito里被称为action。而一个测试项目就是多个测试用例(case)的组合,case之间是对等且隔离的,它们可以共用一些全局项目配置,但是在执行阶段是相互独立的。从 项目 到 用例 再到 行为,mojito是通过上下文系统来使之串联起来。

了解mojito的用法,最好的方式是通过示例工程。每个发布包里都包含一个sample-project目录,里面的yaml文件都包含大量注释或示例以供参考。

目录结构

先来看一下mojito的项目目录结构。打开发布包里的sample-project目录,你会看到如下的结构:

SAMPLE-PROJECT
│  project.yml
│
├─actions
│      call-openapi.yml
│      create-index.yml
│      delete-index.yml
│
├─cases
│      case1.yml
│      case2.yml
│      mysql-case.yml
│
├─data
│      mysql.csv
│      test_main.txt
│
├─mail
│      report.html
│
└─templates

各个目录(文件)的含义如下:

  • project.yml文件(必选):项目的核心定义文件,定义了项目的全局信息(如 项目名称、项目描述等)和配置(如 并行数),也定义了顶层的环境变量(参考 上下文系统)
  • cases目录(必选):所有的用例定义都必须在此目录下,可以有多层的目录
  • actions目录(可选):存放用户的自定义action。在 自定义action 一节会有更详细的描述
  • data目录(可选):存放用户用例中使用到的数据文件。其实mojito并没有规定用户数据文件放到哪儿,只是鼓励用户按照此规则存放
  • mail目录(可选):用于存放用户的邮件模板。(此目录名只是约定,用户可以选用任何目录名)
  • templates目录(保留):未来的mojito还会支持用例模板的功能,所以此目录保留给该功能使用,目前用户只需保留其为空即可

注意:所有mojito文件必须使用“UTF-8无BOM编码”,否则会出现乱码或其他未知错误

Hello Mojito

既然是程序员,第一个程序一定是Hello World。不过此处我们是 Hello Mojito。按照如下步骤,来创建我们的第一个测试项目,开启mojito之旅吧!

  1. 首先,我们创建一个测试项目的根目录,此处我们放在 d:\hello-mojito 下面
  2. 在该目录下创建一个文本文件,project.yml,其内容如下(注意:使用“UTF-8无BOM编码”):

    name: hello-mojito
    desc: this is a HelloWorld project for mojitoenvs:
    - name: defaultdesc: default environment configset-vars:strings:string1: Hellostring2: Mojito
  3. 在该目录下创建一个新目录,命名为 cases

  4. 在cases目录下创建一个文本文件,hello-mojito.yml,其内容如下:

    id: 1
    title: Hello Mojito
    desc: Mojito上的第一个Hello World用例
    envs: [default]
    set-vars:k: ${strings.string1}$ ${strings.string2}$site: http://m.sm.cnsteps:- action: http-actioninvoke-params:method: GETurl: ${site}$/sheaders:User-Agent: "Mozilla/5.0 (Windows NT 6.1; WOW64)"params:q: ${fn:urlescape(k)}$by: submitfrom: smorensure:equals:status: 200
  5. 然后执行指令

    mojito run -p "d:\hello-mojito"

如果一切顺利,你会得到如下的输出:

2015-04-28 18:10:04 - [INFO] start executing project [hello-mojito]
2015-04-28 18:10:04 - [INFO] project params: verbose=0, env=default, tags=[], priority=[3], caselist=[], send-email=[true]
2015-04-28 18:10:04 - [INFO] thread 0 get a case [hello.yml], start to run
2015-04-28 18:10:04 - [INFO] start executing of case [hello.yml], title: Hello Mojito
2015-04-28 18:10:04 - [INFO] enter case startup
2015-04-28 18:10:04 - [INFO] leave case startup, result: success
2015-04-28 18:10:04 - [INFO] enter case steps: null
2015-04-28 18:10:04 - [INFO] start executing of action [http-action]
2015-04-28 18:10:05 - [INFO] action [http-action] successed
2015-04-28 18:10:05 - [INFO] end executing of action [http-action]
2015-04-28 18:10:05 - [INFO] leave case steps, result: success
2015-04-28 18:10:05 - [INFO] enter case teardown
2015-04-28 18:10:05 - [INFO] leave case teardown, result: success
2015-04-28 18:10:05 - [INFO] case [hello.yml] final result: PASS
2015-04-28 18:10:05 - [INFO] end executing of case [hello.yml]
2015-04-28 18:10:05 - [INFO] thread 0 finish case [hello.yml] running
2015-04-28 18:10:05 - [INFO] ================================================================================
2015-04-28 18:10:05 - [INFO]            Project [hello-mojito] results
2015-04-28 18:10:05 - [INFO] ================================================================================
2015-04-28 18:10:05 - [INFO] Summaries:
2015-04-28 18:10:05 - [INFO]    Start: 2015-04-28T10:10:04.754Z       End: 2015-04-28T10:10:05.062Z
2015-04-28 18:10:05 - [INFO]    Total: 1         Pass: 1         Fail: 0         Skip: 0            Timeout: 0
2015-04-28 18:10:05 - [INFO]    Pass Rate: 100.00%              Pure Pass Rate: 100.00%
2015-04-28 18:10:05 - [INFO] --------------------------------------------------------------------------------
2015-04-28 18:10:05 - [INFO] Case deatils:
2015-04-28 18:10:05 - [INFO]    2015-04-28T10:10:05.061Z          PASS      hello.yml
2015-04-28 18:10:05 - [INFO] ================================================================================

一切都很简单自然。我们一共执行了1个case,执行结果是PASS。其中该case(hello.yml)共有3个阶段,分别为startup、steps和teardown。我们在case的定义文件中,只定义了steps,没有定义startup和teardown,所以那两个步骤没有任何action需要执行。在steps阶段,只有一个action,就是http-action。如果在执行的时候使用-v参数,可以得到更详细的log,能够看到action的输入和输出。

再稍微多解释一下case的定义。在hello.yml的steps下面是一个列表,列出了需要执行的每个action,这些action会被顺次执行。此例中我们只有一个http-action调用。在action调用里面,我们传入了两组关键参数,其一是invoke-params,另一个是ensure。

  • invoke-params:其内容为一个map,包含了action执行所需的参数。参数可以是任何类型(string、number、list、map),但其解释权归action所有。此例中的http-action,我们传了4个关键参数,用于调用神马搜索。
  • ensure:定义了对action执行结果的验证,其值为一个map,其中map的key为验证类型。目前mojito只支持3种基本验证类型(equals、matches、exists),未来会支持更多的验证类型,以及验证的逻辑关系(and、or、not)。mojito认为,任何action的执行,其返回值都是一个map、list的组合。可以通过特定的语法来访问action的返回值,从而进行验证。例如在此例中,我们验证返回值的“status”字段(http-action的返回值是一个map)必须等于200,否则就会报错。更多关于ensure的内容参加下文 “action执行结果检验(ensure)”

定义project

在前文已经看到,每个测试工程都会有一个project.yml文件,用于定义工程整体的一些设置。其格式如下:

---
name: sample-project
desc: this is a sample project for opstest
team: OpenSearch
owner: [xu.zhao]
thread: 1envs:
- name: testdesc: test environment configset-vars:taobao-domain: sandbox.taobao.comsearch-domain: sandbox.search.taobao.com
- name: onlineset-vars:taobao-domain: www.taobao.comsearch-domain: s.taobao.com
...

其中各个参数的含义如下:

  • name: 必选,项目的名称。此配置不影响运行,但此参数会在结果统计中用到。建议使用英文名称,并且不使用空格
  • desc: 可选,工程的描述,可随意填写,不影响运行。主要是用于给出工程的更详细描述(如被测对象是什么等)
  • team: 可选,项目所属工作组,可随意填写,不影响运行。但建议填写有意义的名称。
  • owner: 可选,列表形式,任务的所有者(建议填写)。请确保此处填写公司的alias,从而方便与其他系统对接或数据统计
  • thread: 可选,任务运行时使用的并行线程数,默认为 1。mojito支持多线程执行测试用例。在mojito的概念里,case与case之间是完全对等的,且是相互无关的,所以可以随意并行执行。所以case会被放到哪个线程执行是完全无法预期的。即使只有1个线程,case间的先后执行顺序也是不确定的,用户在使用mojito时,不能假设case的执行顺序。如果case间使用了公共资源,且需要互斥访问,可以使用lock方法(后文会有介绍)
  • envs: 必选,为工程定义若干个环境设置。在运行时,可以指定case运行在哪个环境上。请至少指定1个env,否则用例将无法正确运行。测试过程中我们经常会遇到这种情况,你设计了某个case,该case的逻辑非常清晰,且可以运行在多个不同环境上(测试、集成、预发、生产等)。case在不同环境上运行时,测试逻辑完全相同,但配置参数是不同的。mojito的每个env定义,都可以独立定义一组“变量”,在case运行时,会根据所运行的环境不同,而使用不同的env变量定义,从而把一个case“变”成多个环境上的多个case。换句话说,case定义了测试逻辑“模板”,而运行时通过env里的变量设置,来把case模板实例化。
    • name: 必选,env环境的名称(唯一标识)。请使用英文,无空格
    • desc: 可选,此env的附加描述,不影响运行,但建议填写有意义的说明
    • set-vars: 必选,map格式,此env所对应的根变量设置。其内容随意填写,在“上下文系统”一节里会有更详细的使用描述

定义case

在定义好project之后,就可以定义case了。mojito要求所有case必须定义在cases目录下,每个case是一个独立的yaml文件,其文件名(包含相对cases的路径)构成了该case的唯一标识。

用户可以用目录来组织不同的case,但在mojito看来,这些case除了“唯一标识”不同外,没有区别。换句话说,mojito里没有case group的概念。但把case用目录管理起来除了“美观”外,还有一个额外的好处,就是在执行阶段通过-c参数来指定程序路径。通过-c指定的路径是一个正则表达式,所以可以通过这种方式“按目录筛选”case来执行。

case的yaml文件定义如下:

---
id: <id>
title: <case title>
desc: <case description>
envs: [test, pre]
set-vars:var1: [string1, string2]var2: 2var3:var3-1: [311, str312]var3-2:var3-2-1: xxxvar3-2-2: yyyurl: http://${search-domain}$/s
lock: <lock key>startup:- action: sleeplock: <another lock key>invoke-params:second: 2
steps:- action: http-actioninvoke-params:method: GETurl: ${url}$params:q: ${fn:urlescape(k)}$ensure:equals:status: 200
teardown:- action: some-actioninvoke-params:xxx: 1yyy: b
...

其中各个参数的含义如下:

  • id: 必填,数字或字符串,代表用例的id,由用户自己指定,没有特殊含义,也不影响运行(即使不同的case指定了相同的id也没关系)。但建议此处指定唯一的id,而且最好和kelude上的文本case的id一致(如果有文本case的话)。mojito未来会做成一个服务,届时此id将由系统自动生成。
  • title: 必填,字符串,代表程序的名称,由用户自己指定,没有特殊含义,也不影响运行。建议使用可读的简要描述,不宜过长。通常此字段会出现在最终的report中。
  • desc: 选填,字符串,对此用例的相对详细的描述,由用户自己指定,没有特殊含义,也不影响运行。
  • envs: 必填,列表,用于标识此case可以运行在哪些环境上。在mojito的执行阶段,会通过命令行指定一个env,如果该env不在case的envs列表里,则该case不会被执行
  • set-vars: 选填,map格式,用于在case级别定义一组vars。此定义与project.yml里的set-vars一致,只不过在case范围内生效。如果和project里定义了同名的var,则case里的定义会覆盖project里的。这里定义var的时候,可以通过 ${xxx}$ 来引用上下文系统中已经定义的变量,如此例中,我们引用了project里定义的 search-domain 来定义一个新的var,名称为 url。关于 上下文系统 后文还会有详述。
  • lock: 选填,字符串,用于在多线程并行时,进行case间的互斥锁定(类似于 enter-lock)。相同lock key的case不会并行执行。lock可以放在case级别,也可以放在action级别,取决于锁何时锁定和释放。请注意,mojito没有专门处理死锁的机制,需要用户自己来避免。lock名称的定义同样可以引用上下文系统。
  • startup、steps、teardown: 选填,action列表。mojito认为,任何case都是由一组actions组成,startup会被最先执行,然后是steps,最后是teardown。如果startup失败,则steps不会被执行。但teardown始终会被执行,且teardown不可以依赖startup或steps的执行。
  • action格式: action的定义是一个map,分如下字段:

    • action: action的名称(该名称必须是系统已经存在的,内置或自定义均可,关于自定义action后文有详述)。如果action名称不存在,则会报错
    • lock: 与case的lock定义相同,不过只锁定该action的执行阶段
    • set-vars: 定义一组只在action执行阶段有效的vars。
    • invoke-params: 必选,map格式,传递给action执行的参数。其解析由具体的action负责。
    • output-transfer: 可选,用于定义如何对action的输出结果进行转义。举个例子,可能某个http调用的返回值是json(string),但是我们如果直接使用该string进行结果校验,就必须书写大量复杂的正则表达式。那么可以在output-transfer阶段,对结果进行调整,把body里的内容进行json解析,并生成结构化数据,并存储在json-body字段中,那么后续的验证就可以基于此而完成,如下:

      output-transfer:- parse-http-json-body
    • ensure: 可选,map格式,定义如何验证此action的执行结果。如果验证失败,则此action执行失败。具体的结果校验规则,详见 “action执行结果检验(ensure)”

    • export-vars: 可选,map格式,与set-vars类似,其作用是,在action的output里选择“某些”内容,然后输出到上下文系统中。export-vars定义的所有vars,在其后的action执行中是可见的

使用详解

通过上面的Hello Mojito,相信你已经有了初步的使用概念,下面详细展开一些使用细节。

命令行详解

Mojito的命令行模式和hadoop很像,基本是如下的格式:

mojito <command> [parameters...]

目前,command支持如下类型:

  • mojito run: 运行测试用例,其支持参数如下

    • -p project-path: 可选,用于指定待执行的测试工程所在的路径,也就是project.yml所在的路径。默认值为当前路径。
    • -e environment-name: 可选,用于指定所执行的env(前文对env已有介绍)。env是在project.yml里面配置的,这里所指定的名称,必须和配置中的一致。如果不指定,则使用project.yml里配置的第一个env。
    • -c case-path-regex: 可选,可指定多个-c参数,通过正则表达式对case执行路径进行筛选。case执行路径是相对于 /cases 的相对路径。不指定时默认执行所有case。如果指定了多个-c,按“或”关系对case进行筛选。
    • -v: 可选,用于指定程序的verbose level。当添加此参数时,程序会打印更多细节log
    • -t tag: 可选,可指定多个-t参数,通过所指定的tag对case进行筛选。默认执行所有case。当指定多个-t时,case的tag中任何一个匹配到任何一个-t的tag都会得到执行
    • -n exclude-tag: 可选,可指定多个-n。此参数与-t相反,如果匹配,则不执行该case。此参数与-t共同作用时,优先级高于-t
    • -P priority: 可选,可指定多个-P。根据case的优先级定义进行筛选。
    • -m TRUE/FALSE: 可选,用于enable/disable结果邮件的发送。默认不发送结果邮件
  • mojito help: 打印帮助信息

命令行示例:

# 执行当前目录的test project
mojito run# 执行d:\sample-project下的case,在bts环境下执行,用标签【OpenAPI】进行过滤,只执行P1和P2的case
mojito run -p d:\sample-project -e bts -t OpenAPI -P1 -P2

自定义 action

mojito的基本理念就是:case就是action的组合,依次调用action就执行了case。前文我们以http-action为例,讲解了action的基本调用。但细心的话会发现,现有的方式使用起来还是很不方便的。如果我们需要反复调用一个相似的action怎么办?比如在OpenSearch中,所有的API请求都需要进行一个鉴权验证,需要根据http-method、query-paramters、key、secret等信息生成一个token。这个动作会反复出现,难道我们需要每次填写一大堆参数么?Mojito为此提供了一个解决方案,就是 自定义action。

所谓 自定义action,就是从已有的一个或多个action出发,定义一个新的action。类似于面向对象里的类集成或组合。所有的自定义action都需要放到特定的位置:/actions。该目录下的所有yaml文件都会被当做自定义action来解析,使用者可以自主划分多级目录进行组织,每个自定义action一个单独的yaml文件。目前mojito支持3种action扩展,分别是:extend、combine和repeat。

EXTEND-ACTION

extend-action是最常用的自定义action,一般用于简化action的重复调用。其定义格式如下:

---
name: my-extend-action
type: extend-action
base: http-action
required-params: [param1, param2]set-vars:local-var1: ${param1.x}$local-var2: ${param2}$
invoke-params:url: http://xxx.com/api/v2headers:X-API-Header: MyActionmethod: GETparams:some-param: ${local-var1}$another-param: ${local-var2}$
output-transfer:
- parse-http-json-body
ensure:equals:status: 200json-body.status: OK
...

上面的例子示例了extend-action的基本定义方法。my-extend-action从http-action“派生”而来,case里面可以直接调用my-extend-action,但真正执行阶段会去执行http-action。my-extend-action相当于对http-action的调用参数进行了“预设”,或者说是一个调用模板。

开头的四个参数是基本信息:

  • name: 此action的名称,在case里面调用action时,填写此名称。如果定义了同名的自定义action则只有一个定义会被保留,且无法确定保留哪个。目前mojito不做重名检查,需要用户自己保证名称的唯一性。
  • type: 此处填写extend-action,表示对已有的单一action直接进行包装扩展
  • base: 被“派生”的action。执行阶段真正执行到的就是这个action。可以是mojito内置的action,也可以是其他的用户扩展action。mojito在执行阶段才会检查base action的合法性,且目前mojito没有做循环依赖检查,所以用户需要自己保证自定义action之间不会有循环依赖
  • required-params: 必选参数名称列表。当case里调用此action时,mojito会对调用参数做出检查,如果必选参数没有填写,则直接报错。

后面的信息格式与case里调用action的格式是一样的,唯一的区别就是,用户传进来的参数在上下文中也是可用的,在此例中是 param1 和 param2。

COMBINE-ACTION

combine-action是另一种扩展action的方式,其目的是简化多个action的组合调用,把它们抽象成一个公共的action。例如,在OpenSearch测试中,很多case都需要先尝试删除某个表,然后再重新创建该表,则可以把这两个操作组合定义成delete-and-create-table操作,从而简化case中的书写。

---
name: my-combine-action
type: combine-action
required-params: [param1, param2]set-vars:local-var1: ${param1.x}$local-var2: ${param2}$steps:
- action: inner-action-1invoke-params:p1: ${local-var1}$p2: some other info
- action: inner-action-2set-vars:y: ${param1.y}$invoke-params:p1: xyz${local-var2}$p2: hello ${y}$output-transfer:- parse-http-json-bodyensure:equals:json-body.status: OK
...

在这个例子中,我们把两个已有的action(不一定是内置action,有可能是其他自定义action)进行了组合,从而组成了新的my-combine-action,当调用该action时,实际上是依次调用了inner-action1和inner-action-2。前面的几个参数没有什么特别的,而后面steps部分也和case里面的steps格式一样,每个inner action都可以有单独的set-vars、output-transfer和ensure。

整个combine action会最终以最后一个执行到的inner action的output作为整体output。此例中,如果两个inner action都执行成功,则会以inner-action-2的output作为最终output(经过output-transfer之后)。

REPEAT-ACTION

TODO...

optional-params

  • optional-params:action可选参数, 配置默认key-value。当case里调用此action提供默认key的值,使用case action的传入值,未提供时使用定义的默认值 比如,下面默认参数opt-keys1 opt-key2 yaml---name: create-indextype: extend-actionbase: call-openapirequired-params: [host, user, index-name, template, template-type]optional-params:opt-key1: 10opt-key2: 20...### 上下文系统

Mojito的魅力之一就是拥有一套良好的上下文系统,以及功能强大的表达式系统。在前例中我们已经看到,在set-vars中,我们经常看到 ${xxx.yyy}$ 这样的写法,其实就是在引用上下文中的变量。

变量的定义

Mojito对上下文变量的处理类似于编程语言函数调用里面的局部变量。最外层到最内层分别是:

env -> case -> action -> inner action -> inner inner action ...

内层调用可以访问外层的变量,反之则不可以,同名变量内层会覆盖外层。变量定义是直接通过yaml里的map实现的,例如:

set-vars:a: 1b: mojito

上面的代码,分别定义了两个变量,a的值是数字1,b的值是字符串“mojito”。而变量的值可以是“复杂”结构,比如list或map:

set-vars:c:- 1- 2- 3d:m: v1n: v2

需要注意的是,虽然可以定义复杂结构深层次的变量,但变量定义的首层必须是一个map。

变量的引用

Mojito支持在多个地方对定义的变量进行引用,例如在 set-vars 中通过 ${param-name}$ 来访问。所有的数据都是以 map、list、primitive 的某种组合存在的(类似json或yaml所能表述的内容),为了更好的引用变量,Mojito支持通过表达式来进行复杂的访问。假设我们在env中有如下数据:

set-vars:a: 1b: [str1, str2, str3]c:m: abcd:n:- x: 1y: y1- x: 2y: y2- x: 3y: y3
  • 通过key访问map的value

    • a: 直接访问变量a,会得到1
    • c.m: 多层key通过“.”连接,用于访问内层变量,此例中会得到字符串“abc”
  • 通过索引访问列表元素
    • b.[1]:通过下标索引(从0开始)访问列表子元素,此例中会取到“str2”
    • b.[add(-1, 2)]:区别于之前,这里的下标索引不在是特定值,而是来源于函数 add 的调用结果。
  • 调用函数
    • add(5, d.n.[0].x):此处调用了函数add,传入两个参数,第一个是 5,第二个取自d变量第一个元素的x的值,所以,此表达式会得到 6。通过这个例子我们可以看到,函数的参数仍旧可以传一个表达式。
  • 特殊的key
    • <ROOT>:任何时候,通过都可以访问到“根”变量,或者说整个变量表。Mojito为了简化函数调用时候的书写,当以表达式作为函数的参数时,内层表达式的求值会以外层当前访问到的值作为基础。
    • <CUR>:在case的ensure部分,默认访问到的是当前action的output,所有基于key或索引的访问都会从action output出发(这么设计是为了简化书写)。所以在ensure里面, <ROOT> 会访问到整个变量表,而 <CUR> 则会访问到当前action output的根
    • <ITE> 和 <IDX>:这两个特殊的key是用来对list进行筛选时使用的,下面有详细的讲解

在上面的例子中,我们看到Mojito的表达式支持函数调用,此处有必要介绍几个特殊函数

  • filter函数,用于对list进行过滤

    • 格式为 filter(base-path, cond1, cond2 ...),其中base-path对当前数据进行访问,预期取到的是一个list,否则返回null
    • 例子
      • filter(d.n, gt(x, 1)) 的含义为:过滤list,选出所有x元素大于1的那些。此表达式会得到一个list,包含两个元素{x:2, y:y2}和{x:3, y:y3}。
      • filter(d.n, gt(x,1)).[0].y 选出的元素可以继续通过key和索引来访问,此表达式会得到字符串“y2”。
    • 如果函数调用时填写多个condition,则条件间相互为“与”的关系
    • <ITE> 和 <IDX> 的使用
      • filter(d.n, gt(x,1)) 等价于 filter(d.n, gt(<ITE>.x, 1)),ITE为iterator的缩写
      • filter(d.n, gt(<IDX>, 1)) 的含义为:如果list的某个元素“下标”大于1,则将其筛选出来
  • every函数 与filter函数格式类似,不过返回值为True/False。如果list的所有元素都满足条件,则返回Ture,否则返回False
  • debug函数,用于打印更多的帮助信息
    • 格式: debug(inner-expression)
    • 作用: 对内部的表达式进行求值,将求值信息打印在log中,同时返回求值结果。debug函数不对数据产生影响,只是为了打log
  • default函数,如果求值失败,则使用默认值
    • 格式: default(inner-experssion, default-value)
    • 示例: default(xxx.yyy, "hello") 因为xxx.yyy不存在,所以默认值“hello”生效

action结果格式转化(output-transfer)

action的输出直接使用可能会比较“难用”,所以mojito支持对结果进行转化,其方式就是对action的output依次执行一系列的transfer,如下:

- action: http-actioninvoke-params:url: http://xxx.net/apiparams:p1: 1output-transfer:- parse-http-json-body- type: parse-jsonfrom: x.0.yto: another-json-fields- type: simple-select-memberselect:id: ${json-body.id}$info: ${another-json-fields.info}$ensure:equals:id: 1

output-transfer有两种写法,如果没有参数,则直接填写transfer的名称即可,如例子中的 parse-http-json-body。如果有参数,则以map的形式给出,其中type是必选的,填写transfer的名称,其他内容会由具体的transfer来规定。后文介绍 内置output-transfer 时候会给出详细信息

action执行结果检验(ensure)

之前的示例里已经看到,每个action调用都可以通过ensure部分来检查action的返回值是否正确。ensure部分是一个map,其格式如下:

ensure:equals:x: expact1m.0.n: 3matches:y: hello\s+W.*advance:- or(gt(a.b, 1), eq(x, "expact1"))- fun1(x.y, fun2(aaa), 3)

目前支持4种方式对结果进行校验,下面分别介绍

  • equals方式:输入为一个map,其中,key用于对结果进行访问,而值用于表示预期

    • 注意,此处的key虽然也能够像前文介绍的那样通过表达式来访问结果值,但是只支持按照key和index来访问,如此例中的 m.0.n 表示访问结果的key "m" 的 第0个元素 的key "n"。
  • matches方式:与equals类似,不过是做正则匹配
  • exists方式:输入为一个列表,每一项表示对结果的某个字段进行访问,如果得到的值不为空,则通过验证
  • advance方式:输入为一个列表,每行为一个“全功能”的表达式(可以用上下文表达式的任何功能和函数)。如果表达式求值不为空或False,则通过验证。

测试结果展现

目前Mojito在两个地方展现测试结果:邮件和日志。

结果邮件

如果要enable结果邮件,则需要在project.yml里进行正确的配置,下面以注释的方式进行阐述:

# send-email-config: 可选,用于发送结果邮件的smtp陪着。如果不填,则不发送
send-email-config:host: smtp.alibaba-inc.comport: 465ssl: yesuser: xu.zhao@alibaba-inc.compass: "password"# email-to: 选填,结果邮件的发送目标,如果有多个,则用yaml列表方式表# 示。不填写代表不发送结果邮件email-to: [xu.zhao@alibaba-inc.com]# email-from: 建议填写,结果邮件发送时的发送人。如果不填写,则使用# mojito@list.alibaba-inc.com。但是这有可能造成smtp服务器端验证失败而# 拒绝发送邮件email-from: xu.zhao@alibaba-inc.com# email-template: 建议填写,结果邮件的渲染模板。如果不填写,则使用默# 认模板,那是一个很难看的模板哦~~~email-template: mail/report.htmlenvs:
-name: testdesc: test environment config# 下面的3个配置可以覆盖全局的send-email-config里面的配置。如果全局配# 置了邮件发送,但在此env下想disable掉,可以将 email-to 设为空 list,# 如下: email-to: []email-to: [xu.zhao@alibaba-inc.com, other@list.alibaba-inc.com]email-from: xu.zhao@alibaba-inc.comemail-template: mail/report.html

在这个配置里,我们指定了一个邮件渲染的模板,请参考这里来书写你自己的模板(我那个实在太难看了)。该模板使用了一个叫做selmer的模板语言,具体可以参考:这里。也可以参考OpenSearch测试用到的email模板,http://svn.simba.taobao.com/svn/QA/Scripts/Etao/EtaoOpenSearch/opensearch-api-test/mail/report.html

日志

TODO...

内置功能

内置 action 列表

目前mojito已经支持一系列的内置action,每个功能不同,主要体现在调用参数和结果结构的区别。下面详细介绍。

http-action

目前用的最多的action,用于发起http请求,其invoke-params如下:

invoke-params:# 必选,要访问的url(可以不带query string)url: http://xxx.net/api# 可选,http请求的method,默认为GETmethod: POST# 可选,map形式给出query string。例如下面的例子会最终修改url为# http://xxx.net/api?q=hello&uid=12345query-params:q: hellouid: 12345# 可选,以map形式给出PUT、POST请求的form参数form-params:name: xu.zhaoemail: xu.zhao@alibaba-inc.com# 可选,以map形式给出参数,如果请求是put、post,则会合并到form-params里,# 如果是get、head类请求,则会合并到query-params里params:p1: 1p2: pppp# 可选,直接给出put、post请求的http body。如果不填写body-encoding,则默认使用UTF-8body: information for postbody-encoding: UTF-8# 可选,http请求的header,以map形式给出headers:User-Agent: Mozilla/5.0 (Windows NT 6.1;) Gecko/20100101 Firefox/13.0.1foo: [bar, baz]# 可选,不验证SSL server证书的合法性,默认为falseinsecure?: true

http请求是包含cookie的,每个case在启动时会有一个空的cookie,但整个case运行过程中cookie不会被清空,且前面的action的cookie会影响后面的请求。

http-action的结果是个一map,有如下主要字段:

  • body: 返回的body(string格式)
  • headers: response里的headers
  • cookies: server端返回的cookie,以map形式展现,key为cookie的name,其值仍是一个map,为cookie的具体信息,每个cookie都包括如下字段: value、domain、path、discard、secure
  • throw-exceptions: 可选,http请求返回异常status值是否抛异常,默认true。

mysql-query

执行sql查询,并返回其结果。其输入格式如下:

invoke-params:# 必选,指定数据库的链接信息db-spec:host: your.mysql.hostuser: rootpassword: your passwordname: your_db_name# 必选,指定要执行的sqlsql: select * from foo where id > 0 order by id

其返回值是一个map,包含两个字段:

  • count:返回的结果总数
  • results:为一个列表,为所有查到的结果,每个结果是一个map,key为字段名,value为字段值

mysql-execute

执行一条带输入的sql,可以用于创建表或插入数据等,其输入格式如下:

invoke-params:db-spec: 同上省略# 必选,要执行的sql语句,其格式与jdbc中一致,变量部分用?代替sql: insert into foo (id, name) values (?, ?)# 可选,如果sql语句中有“?”,则此处必填,执行时会用此处填写的内容代替问好部分。# 如果执行的建表一类的操作,则不需填写此字段。data:- [1, name1]- [2, name2]

此action的返回值是一个map,只有一个字段,affected,为受影响的结果行数。

mysql-execute-with-csv

此action与mysql-execute类似,不过其输入为csv文件(而非data字段)。

invoke-params:db-spec: 同上省略sql: insert into foo (id, name) values (?, ?)file: data/sqldata.csv

sleep

这是一个没啥用的action,只是使执行线程sleep指定的秒数

invoke-params:second: 30

call-opensearch-api

这是一个专为opensearch的v3版本api调用开发的action

invoke-params:# 必选,opensearch的验证信息access-key-id: your key idaccess-key-secret: your secret# 必选,请求的urlurl: http://xxxx.net/api# 可选,默认为getmethod: get/put/post/....# 可选,内容任意,会被转换成json字符串,然后放到http请求的body里body-params:something: xxxxx# 可选,直接给定json字符串内容,作为请求的body。此字段优先级高于body-params,如果指定# 了此字段,则会忽略body-params的内容body-json: "{\"a\": 1}"# 可选,http请求的headers(与http-action中一致)headers:x-opensearch-foo: bar

其返回值与http-action的返回值格式一致,不过会增加一个额外的字段:json-body,为response的body部分进行json parse的结果。而body里保存的则是原始的body输出。

sh-cmd

这是一个执行shell命令的action

invoke-params:# 必选,要执行的shell命令cmd: your shell command line# 必选,shell运行目录, 可以使用project目录的相对路径或者绝对路径(配置为空时使用project目录)dir: shell execute dictionary# 必选,shell运行需要额外指定的环境变量(不需要时可配为空)env:env_key: xxxx

其返回值是个results的map, exit:return code, out: 标准正确输出, err: 标准错误输出

上下文系统支持的 function 列表

基本运算

  • add/sub/mul/div
  • gt/ge/lt/le/eq

列表运算

  • first/second/last
  • count

逻辑运算

  • and/or/not
  • if

字符串处理

  • upper/lower
  • format(与c语言里的printf语法相同)

时间相关

  • now/local-now/time-to-long/time-from-long
  • date-time:给定年月日时分秒,生成一个datetime对象,例如date-time(1980,1,1,15,30,0)
  • time-plus:将指定时间加上指定偏移(单位:秒),得到新的时间。
  • format-date-time:将时间格式化为字符串,如 format-date-time(now(), "YYYYMMdd")
  • time-gt/time-lt:比较两个时间的大小

其他

  • urlescape: 对给定字符串进行url encode
  • repeat-join: 格式 repeat-join(times, str, sep),将str重复n次,并以sep连接,例如 repeat-join(3, "?", ",") 会得到 "?,?,?"

内置 output transfer 列表

  • parse-http-json-body: 从结果的 body 字段按照json格式parse成结构化数据,然后存储到 json-body 字段中。
  • parse-json:有两个参数,from和to。从output的 from 中取出字符串,按照json格式parse成结构化数据,并存入 to 中。from和to可以是多层的,如a.0.b。
  • simple-select-member:从结果里选出特定的内容,放到新的字段中。此transfer有一个参数 select,其值为一个map,map里面的key为将要写入的字段,而value则为要写入的内容,可以使用前面提到的上下文系统里的全部语法。

其他

Mojito使用Kelude对需求和bug进行管理,项目地址为: http://k3.alibaba-inc.com/project/show?projectId=40655012

已知缺陷

Kelude Link

计划功能

Kelude Link

未来展望

Mojito并不能算作一个多么新颖的设计,只是把已有的各个工具中我们看中的一些功能吸纳了进来。且目前Mojito还有很多整体结构上的不完善,未来还有很多改进方向。此处谈一下目前想到的改进方向。

分布式用例执行

目前mojito是一个单一的java进程,内部采取多线程的方式来提高并行度。可以想见,在某些测试场景,可能需要多机多个“agent”来并行执行测试用例,以加快测试执行速度。

服务式用例托管和执行

mojito当前的定位是一个纯粹的测试工具,用户定义好project、action和case后调用mojito来执行。用户需要自主解决环境部署、用例维护等工作。如果mojito以服务模式运行,允许用户在mojito服务里托管工程和用例,那么也会给mojito增色不少。同时减少一些版本维护等工作。

不过换个角度,用svn或者git管理测试工程,在工程师眼里还是很自然的一件事情,所以从我个人来讲不太认为把mojito作为服务提供有多么大的必要性。

界面化用例编写

mojito完全以yaml来组织工程和用例,而用例只是简单的action的“堆砌”,所以是有可能提供一组界面来简化case、project的编写。

功能扩展

目前想为mojito添加功能扩展,如增添内置action、output-transfer等,只能使用Clojure语言直接添加代码,并且没有明确抽出扩展接口(需要对已有代码足够熟悉才能添加功能)。这部分未来可以定义更加明确的扩展接口,并且支持用其他语言(如python或java)来编写扩展。

mojito 脚本编写相关推荐

  1. 2021年大数据Kafka(三):❤️Kafka的集群搭建以及shell启动命令脚本编写❤️

    全网最详细的大数据Kafka文章系列,强烈建议收藏加关注! 新文章都已经列出历史文章目录,帮助大家回顾前面的知识重点. 目录 系列历史文章 Kafka的集群搭建以及shell启动命令脚本编写 一.搭建 ...

  2. 老李推荐:第3章3节《MonkeyRunner源码剖析》脚本编写示例: MonkeyImage API使用示例 1...

    老李推荐:第3章3节<MonkeyRunner源码剖析>脚本编写示例: MonkeyImage API使用示例 在上一节的第一个"增加日记"的示例中,我们并没有看到日记 ...

  3. Linux编写脚本查看mod,Linux shell脚本编写基础

    在进行linux测试时编写脚本是必不可少的,Shell脚本的名称可以随便定义,也不要什么后缀名,例如可以写abc,smartzip这类名称,运行时只要键入 ./smartzip就能运行脚本了.. 每行 ...

  4. 【Android 内存优化】libjpeg-turbo 函数库交叉编译与使用 ( 交叉编译脚本编写 | 函数库头文件拷贝 | 构建脚本配置 | Android Studio 测试函数库 )

    文章目录 一.交叉编译 Shell 脚本参考 二.NDK r16b 版本配置 三.libjpeg-turbo 交叉编译 Shell 脚本 四.执行 libjpeg-turbo 交叉编译 Shell 脚 ...

  5. SecureCRT中Python脚本编写学习指南

    引言 在测试网络设备中,通常使用脚本对设备端进行配置和测试以及维护:对于PE设备的测试维护人员来说使用较多是SecureCRT工具:SecureCRT支持VB.JavaScript.Python等多种 ...

  6. 使用脚本编写 Vim 编辑器,第 5 部分: 事件驱动的脚本编写和自动化

    Vim 的事件模型 Vim 编辑功能的运行方式是事件驱动的.但由于性能上的原因,实际的实现要远比这个复杂,还需要进行许多事件处理优化或者处理事件循环下面的几层,但是您仍然可以将编辑器看成一个简单循环, ...

  7. 使用脚本编写 Vim 编辑器,第 4 部分: 字典

    Vimscript 中的字典 在本质上和 AWK 关联数组.Perl 哈希表,或者 Python 字典都是一样.也就是说,这是一个无序容器,按字符串而不是整数来进行索引. Vimscript 系列 的 ...

  8. 使用脚本编写 Vim 编辑器,第 2 部分: 用户定义函数

    用户定义函数 Haskell 或 Scheme 程序员会告诉您,函数对于任何严肃的编程语言来说都是最重要的特性.对于 C 或 Perl 程序员,他们也会告诉您完全相同的观点. 函数为严肃的程序员提供了 ...

  9. 命令测试post_性能测试脚本编写之三

    >>>推荐阅读<<< 1.性能测试学习笔记-场景设计 2.性能测试的重要意义 3.性能分析流程及方法 4.应用系统性能调优之性能分析 ### web_url ### ...

最新文章

  1. IOS第三天(@property与@synthesize的用法)
  2. Fotosifter中文版
  3. Anaconda:Anaconda安装pytorch网络连接失败记录
  4. 利用nginx的301重定向到另外服务器
  5. 将Eclipse Android项目打包成APK文件
  6. echarts js 删除框选数据_ECharts进行区域选择
  7. 用python画熊猫代码_python-使用Pandas绘制包含列表的列
  8. jquery html dom方式创建新html元素
  9. Jupyterlab 执行时间插件
  10. android phone驱动 华为,华为智能手机USB驱动
  11. Linux系统下如何解压压缩包内某个文件
  12. Word编辑页码不从第一页开始
  13. 高清影音发展的产物格式转换器
  14. 计算机上发出打印命令,从计算机上发送打印命令后打印机不打印怎么办
  15. 加菲猫台词 (请对号入座-:))
  16. java毕业设计视频点播系统Mybatis+系统+数据库+调试部署
  17. 闲来没事自己研究了下RBAC
  18. 点击父组件按钮 显示子组件_按钮设计用户界面组件系列
  19. Java+webdriver的自动化测试框架搭建
  20. 我的世界服务器附魔修改器,我的世界附魔辅助器

热门文章

  1. 【Latex】算法排版规律(中文排版)
  2. Android 实现汉字转成拼音,实现ListView的A-Z字母排序,显现多音字城市排序
  3. 世界五百强企业网站(一)
  4. 短视频课程内容培训(短视频培训课程大纲)
  5. java script 截断_JavaScript截取字符串的最后几个字符
  6. 手机html5测试苹果八,iPhone8和iPhone8 Plus哪个更值得买?苹果8和苹果8plus全面对比深度评测...
  7. CSP 201903-1 大中小
  8. 历经千辛万苦的Ubuntu20.04与Geomagic Haptic相联(成功一半)
  9. 量子搜索算法(Grover Algorithm)
  10. 如何将上传到优酷的视频加载到网页中?