如果项目需求是从某些复杂的json里面取值进行计算,用jsonpath+IK(ik-expression)来处理十分方便,jsonpath用来取json里面的值然后用IK自带的函数进行计算,如果是特殊的计算那就自定义IK方法搞定,配置化很方便.
下面简单介绍下jsonpath的使用方法,主要测试都在JsonPathDemo类里面:
下面是一个简单的java项目demo:
注意: 其中他的max,min,avg,stddev函数只能类似于如下处理:
1
2
|
//正确写法 但是感觉很鸡肋
context.read( "$.avg($.result.records[0].loan_type,$.result.records[1].loan_type,$.result.records[2].loan_type)" );
|
不能传入list 感觉比较鸡肋,如果传入list 他会报错(如下错误写法):
1
2
3
4
5
|
//这样会报错
Object maxV = context.read( "$.max($.result.records[*].loan_type)" );
//这样也会报错
Object maxV = context.read( "$.result.records[*].loan_type.max()" );
//如果json文件中是这样:"loan_type":"2",也会报错,"loan_type":2 这样才被认为是数字
|
报错信息都一样, 如下:
Exception in thread "main" com.jayway.jsonpath.JsonPathException: Aggregation function attempted to calculate value using empty array
JsonPathDemo是一个测试demo:
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
public class JsonPathDemo {
public static void main(String[] args) {
String json = FileUtils.readFileByLines( "demo.json" );
ReadContext context = JsonPath.parse(json);
//1 返回所有name
List<String> names = context.read( "$.result.records[*].name" );
//["张三","李四","王五"]
System.out.println(names);
//2 返回所有数组的值
List<Map<String, String>> objs = context.read( "$.result.records[*]" );
//[{"name":"张三","pid":"500234199212121212","mobile":"18623456789","applied_at":"3","confirmed_at":"5","confirm_type":"overdue","loan_type":"1","test":"mytest","all":"2"},{"name":"李四","pid":"500234199299999999","mobile":"13098765432","applied_at":"1","confirmed_at":"","confirm_type":"overdue","loan_type":"3","all":"3"},{"name":"王五","pid":"50023415464654659","mobile":"1706454894","applied_at":"-1","confirmed_at":"","confirm_type":"overdue","loan_type":"3"}]
System.out.println(objs);
//3 返回第一个的name
String name0 = context.read( "$.result.records[0].name" );
//张三
System.out.println(name0);
//4 返回下标为0 和 2 的数组值
List<String> name0and2 = context.read( "$.result.records[0,2].name" );
//["张三","王五"]
System.out.println(name0and2);
//5 返回下标为0 到 下标为1的 的数组值 这里[0:2] 表示包含0 但是 不包含2
List<String> name0to2 = context.read( "$.result.records[0:2].name" );
//["张三","李四"]
System.out.println(name0to2);
//6 返回数组的最后两个值
List<String> lastTwoName = context.read( "$.result.records[-2:].name" );
//["李四","王五"]
System.out.println(lastTwoName);
//7 返回下标为1之后的所有数组值 包含下标为1的
List<String> nameFromOne = context.read( "$.result.records[1:].name" );
//["李四","王五"]
System.out.println(nameFromOne);
//8 返回下标为3之前的所有数组值 不包含下标为3的
List<String> nameEndTwo = context.read( "$.result.records[:3].name" );
//["张三","李四","王五"]
System.out.println(nameEndTwo);
//9 返回applied_at大于等于2的值
List<Map<String, String>> records = context.read( "$.result.records[?(@.applied_at >= '2')]" );
//[{"name":"张三","pid":"500234199212121212","mobile":"18623456789","applied_at":"3","confirmed_at":"5","confirm_type":"overdue","loan_type":"1","test":"mytest","all":"2"}]
System.out.println(records);
//10 返回name等于李四的值
List<Map<String, String>> records0 = context.read( "$.result.records[?(@.name == '李四')]" );
//[{"name":"李四","pid":"500234199299999999","mobile":"13098765432","applied_at":"1","confirmed_at":"","confirm_type":"overdue","loan_type":"3"}]
System.out.println(records0);
//11 返回有test属性的数组
List<Map<String, String>> records1 = context.read( "$.result.records[?(@.test)]" );
//[{"name":"张三","pid":"500234199212121212","mobile":"18623456789","applied_at":"3","confirmed_at":"5","confirm_type":"overdue","loan_type":"1","test":"mytest","all":"2"}]
System.out.println(records1);
//12 返回有test属性的数组
List<String> list = context.read( "$..all" );
//["1","4","2","3"]
System.out.println(list);
//12 以当前json的某个值为条件查询 这里ok为1 取出records数组中applied_at等于1的数组
List<String> ok = context.read( "$.result.records[?(@.applied_at == $['ok'])]" );
//["1","4","2","3"]
System.out.println(ok);
//13 正则匹配
List<String> regexName = context.read( "$.result.records[?(@.pid =~ /.*999/i)]" );
//[{"name":"李四","pid":"500234199299999999","mobile":"13098765432","applied_at":"1","confirmed_at":"","confirm_type":"overdue","loan_type":"3","all":"3"}]
System.out.println(regexName);
//14 多条件
List<String> mobile = context.read( "$.result.records[?(@.all == '2' || @.name == '李四' )].mobile" );
//["18623456789","13098765432"]
System.out.println(mobile);
//14 查询数组长度
Integer length01 = context.read( "$.result.records.length()" );
//3
System.out.println(length01);
//15 查询list里面每个对象长度
List<Integer> length02 = context.read( "$.result.records[?(@.all == '2' || @.name == '李四' )].length()" );
//[9,8]
System.out.println(length02);
//16 最大值
Object maxV = context.read( "$.max($.result.records[0].loan_type,$.result.records[1].loan_type,$.result.records[2].loan_type)" );
//3.0
System.out.println(maxV);
//17 最小值
Object minV = context.read( "$.min($.result.records[0].loan_type,$.result.records[1].loan_type,$.result.records[2].loan_type)" );
//1.0
System.out.println(minV);
//18 平均值
double avgV = context.read( "$.avg($.result.records[0].loan_type,$.result.records[1].loan_type,$.result.records[2].loan_type)" );
//2.3333333333333335
System.out.println(avgV);
//19 标准差
double stddevV = context.read( "$.stddev($.result.records[0].loan_type,$.result.records[1].loan_type,$.result.records[2].loan_type)" );
//0.9428090415820636
System.out.println(stddevV);
//20 读取一个不存在的
String haha = context.read( "$.result.haha" );
//抛出异常
//Exception in thread "main" com.jayway.jsonpath.PathNotFoundException: No results for path: $['result']['haha']
//at com.jayway.jsonpath.internal.path.EvaluationContextImpl.getValue(EvaluationContextImpl.java:133)
//at com.jayway.jsonpath.JsonPath.read(JsonPath.java:187)
//at com.jayway.jsonpath.internal.JsonContext.read(JsonContext.java:102)
//at com.jayway.jsonpath.internal.JsonContext.read(JsonContext.java:89)
//at cn.lijie.jsonpath.JsonPathDemo.main(JsonPathDemo.java:58)
//at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
//at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
//at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
//at java.lang.reflect.Method.invoke(Method.java:498)
//at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
System.out.println(haha);
}
}
|
pom文件引入:
1
2
3
4
5
|
< dependency >
< groupId >com.jayway.jsonpath</ groupId >
< artifactId >json-path</ artifactId >
< version >2.3.0</ version >
</ dependency >
|
其中demo.json是一个测试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
38
39
40
41
42
43
44
45
|
{
"action" : "/interface.service/xxx/queryBlackUserData" ,
"all" : "1" ,
"result" : {
"count" : 2 ,
"tenant_count" : 2 ,
"records" : [
{
"name" : "张三" ,
"pid" : "500234199212121212" ,
"mobile" : "18623456789" ,
"applied_at" : "3" ,
"confirmed_at" : "5" ,
"confirm_type" : "overdue" ,
"loan_type" : 1 ,
"test" : "mytest" ,
"all" : "2"
},
{
"name" : "李四" ,
"pid" : "500234199299999999" ,
"mobile" : "13098765432" ,
"applied_at" : "1" ,
"confirmed_at" : "" ,
"confirm_type" : "overdue" ,
"loan_type" : 3 ,
"all" : "3"
},
{
"name" : "王五" ,
"pid" : "50023415464654659" ,
"mobile" : "1706454894" ,
"applied_at" : "-1" ,
"confirmed_at" : "" ,
"confirm_type" : "overdue" ,
"loan_type" : 3
}
],
"all" : "4"
},
"code" : 200 ,
"subtime" : "1480495123550" ,
"status" : "success" ,
"ok" : 3
}
|
FileUtils类是用于读取xx.json文件为字符串的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
|
public class FileUtils {
/**
* 以行为单位读取文件,常用于读面向行的格式化文件
*/
public static String readFileByLines(String fileName) {
File file = new File(fileName);
BufferedReader reader = null ;
String str = "" ;
try {
InputStream is = FileUtils. class .getClassLoader().getResourceAsStream(fileName);
reader = new BufferedReader( new InputStreamReader(is));
String tempString = null ;
int line = 1 ;
// 一次读入一行,直到读入null为文件结束
while ((tempString = reader.readLine()) != null ) {
// 显示行号
str += tempString;
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null ) {
try {
reader.close();
} catch (IOException e1) {
}
}
}
return str;
}
}
|
补充:json接口测试的利器jsonpath
在测试REST接口的时候,经常要解析JSON,那么可以使用开源jsonpath进行,其中看网上看到相关的说法不错的使用场景为:
1、接口关联
也称为关联参数。在应用业务接口中,完成一个业务功能时,有时候一个接口可能不满足业务的整个流程逻辑,需要多个接口配合使用,简单的案例如:B接口的成功调用依赖于A接口,需要在A接口的响应数据(response)中拿到需要的字段,在调用B接口的时候,传递给B接口作为B接口请求参数,拿到后续响应的响应数据。
接口关联通常可以使用正则表达式去提取需要的数据,但对于json这种简洁、清晰层次结构、轻量级的数据交互格式,使用正则未免有点杀鸡用牛刀的感觉(是的,因为我不擅长写正则表达式),我们需要更加简单、直接的提取json数据的方式。
2、数据验证
这里的数据验证指的是对响应结果进行数据的校验
接口自动化测试中,对于简单的响应结果(json),可以直接和期望结果进行比对,判断是否完全相等即可。
如 json {"status":1,"msg":"登录成功"}
3、对于格式较复杂
尤其部分数据存在不确定性、会根据实际情况变化的响应结果,简单的判断是否完全相等(断言)通常会失败。
如:
1
|
json { "status" : 1 , "code" : "10001" , "data" :[{ "id" : 1 , "investId" : "1" , "createTime" : "2018-04-27 12:24:01" , "terms" : "1" , "unfinishedInterest" : "1.0" , "unfinishedPrincipal" : "0" , "repaymentDate" : "2018-05-27 12:24:01" , "actualRepaymentDate" : null , "status" : "0" },{ "id" : 2 , "investId" : "1" , "createTime" : "2018-04-27 12:24:01" , "terms" : "2" , "unfinishedInterest" : "1.0" , "unfinishedPrincipal" : "0" , "repaymentDate" : "2018-06-27 12:24:01" , "actualRepaymentDate" : null , "status" : "0" },{ "id" : 3 , "investId" : "1" , "createTime" : "2018-04-27 12:24:01" , "terms" : "3" , "unfinishedInterest" : "1.0" , "unfinishedPrincipal" : "100.00" , "repaymentDate" : "2018-07-27 12:24:01" , "actualRepaymentDate" : null , "status" : "0" }], "msg" : "获取信息成功" }
|
上面的json结构嵌套了很多信息,完整的匹配几乎不可能成功。比如其中的createTime信息,根据执行接口测试用例的时间每次都不一样。同时这个时间是响应结果中较为次要的信息,在进行接口自动化测试时,是可以选择被忽略的。
4、我们需要某种简单的方法
能够从json中提取出我们真正关注的信息(通常也被称为关键信息)。
如提取出status的值为1,data数组中每个对象的investId都为1,data中第三个对象的unfinishedPrincipal值为100.00,只要这三个关键信息校验通过,我们就认为响应结果没有问题。
JSONPATH有点像XPATH了,语法规则小结下:
这里有个表格,说明JSONPath语法元素和对应XPath元素的对比。
XPath | JSONPath | Description |
/ | $ | 表示根元素 |
. | @ | 当前元素 |
/ | . or [] | 子元素 |
.. | n/a | 父元素 |
// | .. | 递归下降,JSONPath是从E4X借鉴的。 |
* | * | 通配符,表示所有的元素 |
@ | n/a | 属性访问字符 |
[] | [] |
子元素操作符 |
| | [,] |
连接操作符在XPath 结果合并其它结点集合。JSONP允许name或者数组索引。 |
n/a | [start:end:step] |
数组分割操作从ES4借鉴。 |
[] | ?() |
应用过滤表示式 |
n/a | () |
脚本表达式,使用在脚本引擎下面。 |
() | n/a | Xpath分组 |
下面是一个简单的json数据结构代表一个书店(原始的xml文件是)
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
|
{ "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
}
}
}
|
XPath | JSONPath | 结果 |
/store/book/author | $.store.book[*].author |
书点所有书的作者 |
//author | $..author |
所有的作者 |
/store/* | $.store.* |
store的所有元素。所有的bookst和bicycle |
/store//price | $.store..price |
store里面所有东西的price |
//book[3] | $..book[2] |
第三个书 |
//book[last()] | $..book[(@.length-1)] | 最后一本书 |
//book[position()<3] |
$..book[0,1]
$..book[:2] |
前面的两本书。 |
//book[isbn] | $..book[?(@.isbn)] | 过滤出所有的包含isbn的书。 |
//book[price<10] | $..book[?(@.price<10)] | 过滤出价格低于10的书。 |
//* | $..* |
所有元素。 |
比如在单元测试MOCK中,就可以这样使用:
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
|
@RunWith (SpringRunner. class )
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles ( "test" )
public class BookControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private BookRepository mockRepository;
/*
{
"timestamp":"2019-03-05T09:34:13.280+0000",
"status":400,
"errors":["Author is not allowed.","Please provide a price","Please provide a author"]
}
*/
//article : jsonpath in array
@Test
public void save_emptyAuthor_emptyPrice_400() throws Exception {
String bookInJson = "{\"name\":\"ABC\"}" ;
mockMvc.perform(post( "/books" )
.content(bookInJson)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath( "$.timestamp" , is(notNullValue())))
.andExpect(jsonPath( "$.status" , is( 400 )))
.andExpect(jsonPath( "$.errors" ).isArray())
.andExpect(jsonPath( "$.errors" , hasSize( 3 )))
.andExpect(jsonPath( "$.errors" , hasItem( "Author is not allowed." )))
.andExpect(jsonPath( "$.errors" , hasItem( "Please provide a author" )))
.andExpect(jsonPath( "$.errors" , hasItem( "Please provide a price" )));
verify(mockRepository, times( 0 )).save(any(Book. class ));
}
}
|
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。如有错误或未考虑完全的地方,望不吝赐教。
原文链接:https://blog.csdn.net/qq_20641565/article/details/77162868