在实际的接口测试时,传参有时候可能需要很多,也可能我们就是想要一份完整的参数,必填项和非必填项都包含在内,比如下面的 json
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| { "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } }, "expensive": 10 }
|
一个个在方法中传入显然不现实;写入hashmap
中传入的话工作量和复杂度也很大,这个时候就需要一个模板,把我们需要的参数和结构提前定义好,我们只需要修改其中对应的值即可,这就要引出今天的两位主角:
JsonPath
参考文档
先来看第一个模板技术JsonPath
,注意这里的JsonPath
指的是Jayway JsonPath
,maven依赖如下:
1 2 3 4 5
| <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <version>2.5.0</version> </dependency>
|
元素定位
在UI自动化中我们会使用到xpath的元素定位的技术,根据对比,json也有JsonPath的元素定位,为了方便理解记忆,我们可以Xpath与JsonPath进行对比
Xpath |
JSONPath |
描述 |
/ |
$ |
文档根元素 |
. |
@ |
当前元素 |
/ |
. or[] |
匹配下级元素 |
.. |
N/A |
匹配上级元素,JsonPath不支持此操作符 |
// |
.. |
递归匹配所有子元素 |
* |
* |
通配符,匹配下级元素 |
@ |
N/A |
匹配属性,JsonPath不支持此操作符 |
[] |
[] |
下标运算符,根据索引获取元素,XPath索引从1开始,JsonPath索引从0开始 |
` |
` |
[,] |
N/A |
[start:end:step] |
数据切片操作,XPath不支持 |
[] |
?() |
过滤表达式 |
N/A |
() |
脚本表达式,使用底层脚本引擎,XPath不支持 |
() |
N/A |
分组,JsonPath不支持 |
注意:
- JsonPath的索引从0开始计数
- JsonPath中字符串使用单引号表示,例如:
$.store.book[?(@.category=='reference')]
中的’reference’
jsonPath的语法要点:
- $ 表示文档的根元素
- @ 表示文档的当前元素
- .node_name 或 [‘node_name’] 匹配下级节点
- [index] 检索数组中的元素
[start:end:step]
支持数组切片语法
*
作为通配符,匹配所有成员
- .. 子递归通配符,匹配成员的所有子元素
(<expr>)
使用表达式
?(<boolean expr>)
进行数据筛选
工具类封装
JsonPathUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package com.api.object.utils;
import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import java.util.Map;
public class JsonPathUtils {
private static final Logger logger = LoggerFactory.getLogger(JsonPathUtils.class);
private final String path;
private final Map<String, Object> content;
public JsonPathUtils(String path, Map<String, Object> content) { this.path = path; this.content = content; }
public String modifyResult() { logger.info("读取JSON文件:{},替换的内容:{}", this.path, JSONUtil.parse(content)); DocumentContext documentContext = JsonPath.parse(this.getClass().getResourceAsStream(this.path)); for (Map.Entry<String, Object> entry : this.content.entrySet()) { documentContext.set(entry.getKey(), entry.getValue()); } return documentContext.jsonString(); } }
|
示例演示
以下实操演示均先分为以下三步:
- 以
Jayway JsonPath
的parse方法对json文件内容进行解析获取
- 再使用
set
方法以Jayway JsonPath
的语法对解析结果进行修改
- 最后使用json工具对结果进行格式化输出,查看修改结果
示例1:$.store.book[*].author
——所有书的作者,将所有书的作者名都改为”测试作者”
1 2 3 4 5 6 7 8 9 10
|
@Test void modifyResult() { Map<String, Object> content = new HashMap<>(); content.put("$.store.book[*].author", "测试作者"); String result = new JsonPathUtils("/json/book.json", content).modifyResult(); System.out.println(JSONUtil.parseObj(result)); }
|
执行完成,得到结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| { "store": { "bicycle": { "color": "red", "price": 19.95 }, "book": [{ "author": "测试作者", "title": "Sayings of the Century", "price": 8.95, "category": "reference" }, { "author": "测试作者", "title": "Sword of Honour", "price": 12.99, "category": "fiction" }, { "author": "测试作者", "isbn": "0-553-21311-3", "title": "Moby Dick", "price": 8.99, "category": "fiction" }, { "author": "测试作者", "isbn": "0-395-19395-8", "title": "The Lord of the Rings", "price": 22.99, "category": "fiction" }] }, "expensive": 10 }
|
示例2:$.store.*
——store下所有的都进行修改,这里包括了bicycle和book。将store下的所有内容改为all change
1 2 3 4 5 6 7 8 9 10
|
@Test void modifyResult02() { Map<String, Object> content = new HashMap<>(); content.put("$.store.*", "all change"); String result = new JsonPathUtils("/json/book.json", content).modifyResult(); System.out.println(JSONUtil.parseObj(result)); }
|
执行完成,得到结果
1 2 3 4 5 6 7
| { "store": { "bicycle": "all change", "book": "all change" }, "expensive": 10 }
|
示例3:$..book[0,1]
——book下的第一个和第二个,将book下的第一个和第二个内容改为first two change
1 2 3 4 5 6 7 8 9 10
|
@Test void modifyResult03() { Map<String, Object> content = new HashMap<>(); content.put("$..book[0,1]", "first two change"); String result = new JsonPathUtils("/json/book.json", content).modifyResult(); System.out.println(JSONUtil.parseObj(result)); }
|
执行完成,得到结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| { "store": { "bicycle": { "color": "red", "price": 19.95 }, "book": [ "first two change", "first two change", { "author": "Herman Melville", "isbn": "0-553-21311-3", "title": "Moby Dick", "price": 8.99, "category": "fiction" }, { "author": "J. R. R. Tolkien", "isbn": "0-395-19395-8", "title": "The Lord of the Rings", "price": 22.99, "category": "fiction" } ] }, "expensive": 10 }
|
Mustache
mustache官方文档

引出这张图,还为了说明一点,可以看出Mustache支持各种语言,常用的几乎都能囊括其中
概述
Mustache属于无逻辑模板引擎,因为其不支持if-else和for语句,主要是有双花括号括起来的模板变量及包含模板数据的模型对象组成,因为双括号看起来像胡子,因此得名mustache。
环境:java8 + maven,引入相应的依赖
1 2 3 4 5 6
| <dependency> <groupId>com.github.spullara.mustache.java</groupId> <artifactId>compiler</artifactId> <version>0.9.7</version> </dependency>
|
工具类封装
- MustacheUtils.java
- MustacheFactory 在类路径下搜索模板文件,我们的模板文件在src/main/resources路径下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.api.object.utils;
import cn.hutool.json.JSONUtil; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.StringWriter;
public class MustacheUtils {
private static final Logger logger = LoggerFactory.getLogger(MustacheUtils.class);
private final String path;
private final Object model;
public MustacheUtils(String path, Object model) { this.path = path; this.model = model; }
public String execute() { logger.info("读取模板文件:{},替换的内容:{}", this.path, JSONUtil.parse(model)); StringWriter writer = new StringWriter();
MustacheFactory mf = new DefaultMustacheFactory(); Mustache mustache = mf.compile(this.path); try { mustache.execute(writer, this.model).flush(); } catch (IOException e) { logger.error("模板内容解析异常:{}", e.getMessage()); } return writer.toString(); } }
|
示例演示
首先,在需要修改的文件中,将要修改的字段用双花括号{{变量名}}
的形式表示,例如这里我们将book下第一个商品的author和price进行变量化,在resources目录下创建book.mustache文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| { "store": { "book": [ { "category": "reference", "author": "{{author}}", "title": "Sayings of the Century", "price": {{price}} }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } }, "expensive": 10 }
|
测试代码:
1 2 3 4 5 6 7 8
| @Test void execute01() { Map<String, Object> map = new HashMap<>(); map.put("author", "mustacheAuthor"); map.put("price", 56.8f); String s = new MustacheUtils("mustache/book.mustache", map).execute(); System.out.println(JSONUtil.parse(s)); }
|
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| { "store": { "bicycle": { "color": "red", "price": 19.95 }, "book": [{ "author": "mustacheAuthor", "title": "Sayings of the Century", "price": 56.8, "category": "reference" }, { "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99, "category": "fiction" }, { "author": "Herman Melville", "isbn": "0-553-21311-3", "title": "Moby Dick", "price": 8.99, "category": "fiction" }, { "author": "J. R. R. Tolkien", "isbn": "0-395-19395-8", "title": "The Lord of the Rings", "price": 22.99, "category": "fiction" }] }, "expensive": 10 }
|
Mustache不仅仅支持JSON文件的模板,还支持非JSON文件的模板,下面就以HTML文件模板介绍:
新建一个简单的模板,在resources文件下,创建文件mustache/todo.mustache,内容如下:
1 2 3
| <h2>{{title}}</h2> <small>Created on {{createdOn}}</small> <p>{{text}}</p>
|
在{{}}
中的模板变量可以是Java类的方法和属性,也是Map对象的key。
提供模板数据是Todo类的实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.mustache.demo.entity;
import lombok.Data;
import java.util.Date;
@Data public class Todo { private String title; private String text; private boolean done; private Date createdOn; private Date completedOn; }
|
执行模板生成HTML内容的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.test;
import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; import com.test.pojo.Todo;
import java.io.IOException; import java.io.StringWriter; import java.util.Date;
public class Run { public static void main(String[] args) throws IOException { Todo todo = new Todo(); todo.setTitle("Todo1"); todo.setText("第一个模板"); todo.setCreatedOn(new Date()); MustacheFactory mf = new DefaultMustacheFactory(); Mustache m = mf.compile("mustache/todo.mustache");
StringWriter writer = new StringWriter(); m.execute(writer, todo).flush(); String html = writer.toString(); System.out.println(html); } }
|
输出的结果:

优缺点对比
如果实践了上面的两种模板方法会发现两者在使用上的优缺点:
优点:
- JsonPath:可设置默认值,在json文件中可提前设置好默认值,字段不修改就使用默认值,需要修改再进行修改
- Mustache:不受文件格式的影响,字段替换逻辑也简单清晰
缺点:
- JsonPath:只适用于json文件
- Mustache:灵活度不如JsonPath,提前必须指定好需要修改的字段
总结:
如果接口的请求体都是以json为主的,那么我个人推荐使用JsonPath,更灵活的控制接口参数的修改程度;当遇到非json格式的文件时可用Mustache来解决模板化的问题,只是灵活度要稍稍下降一点了可能。