美文网首页
还在用equals来做对象断言么?

还在用equals来做对象断言么?

作者: antony已经被占用 | 来源:发表于2020-05-30 15:21 被阅读0次

断言需求分析

在HTTP接口自动化测试时,如果接口返回是JSON格式的结果,通常可以用Sting比较的方式进行断言,或者是经过反序列化形成对象或者对象数组,通过对象间Equals的方法进行断言。

BlogDTO retrievedBlog = given() .spec(spec) .
when().get(locationHeader) .
then() .statusCode(200) .extract().as(BlogDTO.class); 
assertThat(retrievedBlog.getName()).
isEqualTo(newBlog.getName()); 
assertThat(retrievedBlog.getDescription()).
isEqualTo(newBlog.getDescription()); 
assertThat(retrievedBlog.getUrl()).isEqualTo(newBlog.getUrl());

来源
当然,两种方式的断言的适用场景是非常有限的在实际的应用场景中,可能不仅仅是简单的相等而已,那么预期结果和实际结果比对的常见场景是这样的:

  • 时间戳、序列号等数据的处理

一般可以忽略比比较或者通过模式匹配来断言其格式是否正确。

  • 包含关系,而不是相等关系

如预期结果是实际结果的一个子集,或者说预期对象只包含了实际对象的某一部分属性。

  • 出现顺序

类似包含关系,如数据集中记录的顺序或者是一个对象的属性的顺序。

实际工作中往往需要处理上述场景,甚至是场景的组合,才能正确地完成断言。
可能有读者会说,是否可以通过重写对象的Equals方法或者是toString方法来进行,实现上述的数据处理和断言的需求呢?理论上这是可行的,但这种方法

  1. 需要对业务对象代码作出修改,而且业务对象众多时修改代码量较大。
  2. 不同断言场景下如果对需要修改的部分不一致,则无法支持。

可见这是一种不是很经济的做法,也不能灵活支持各种断言需求。

解决方案

就本小节开篇所说 ,由于接口调用结果为JSON格式,自然考虑可以用使用JSON格式相关的方案来进行接口自动化的断言。另外在单元测试等场景中,只要是对比较复杂的对象进行断言,也可以考虑将对象通过序列化变成JSON格式后再通过上述JSON断言的方式来进行。因此,这个方案其实是有其通用性的。对于JSON断言的工具,笔者推荐JsonUnit这一工具。它提供了兼容AssertJ断言的接口,对于习惯了AssertJ的开发人员非常友好,而且支持JsonPath等传统的JSON解析和断言方式。最重要的是,它提供了非常丰富的内置方法来协助实现本小节中提出的JSON断言需求,甚至更多。

JsonUnit####

要使用该工具,首先在项目中引入依赖

<dependency>
    <groupId>net.javacrumbs.json-unit</groupId>
    <artifactId>json-unit-assertj</artifactId>
    <version>2.17.0</version>
    <scope>test</scope>
</dependency>

然后,就可以使用assertThatJson断言方法开始进行断言。使用体验和AssertJ提供的assertThat非常类似。

import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
// 以下案例绝大部分来自官网
assertThatJson("{\"a\":1, \"b\":2}").isEqualTo("{b:2, a:1}");

忽略值Ignoring values

在应用日志中,出于保护客户隐私信息的考虑,会将手机号、密码、身份证号等信息的进行模糊化处理,譬如将手机号中间4位用占位符****进行遮罩。当比较两个JSON时,在某些情况下,也需要对譬如时间戳、序列号、价格等字段值进行忽略。

JSONUnit提供了 ${json-unit.ignore} 的占位符,来实现这一功能。例如是对一个凭单号的断言,

String result="{\"data\":{\"vourcherID\":202005278899, \"bankID\": \"01\"}}";
String expected="{\"data\":{\"vourcherID\":\"${json-unit.ignore}\", \"bankID\": \"01\"}}"
assertThatJson(result).
isEqualTo(expected);

忽略元素Ignoring elements

由于 ${json-unit.ignore} 只是对值的忽略,前例中如果vourcherID这个元素不存在的话,断言会失败。在某些情况下,如果需要对整个元素进行忽略,则需要使用${json-unit.ignore-element} 这一占位符。

assertThatJson("{\"data\":{\"vourcherID\":202005278899, \"bankID\": \"01\"}}")
.isEqualTo("{\"data\":{\"vourcherID\":\"${json-unit.ignore-element}\", \"bankID\": \"01\"}}");

忽略路径Ignoring paths

某些情况下,需要在匹配时来指定忽略某个JSON路径(path),而不是只指定某个具体的元素。JsonUnit 提供了whenIgnoringPaths 的配置选项来实现这一需求。如下例:

assertThatJson("{\"root\":{\"test\":1, \"ignored\": 1}}")
    .whenIgnoringPaths("root.ignored"))
    .isEqualTo("{\"root\":{\"test\":1}}");

正则表达式 Regular expressions

对于电话号码、序列号等有规则的非固定数据,在匹配时除了忽略以外,还可以对其进行一定程度的断言,如通过正则表达式进行格式校验。JsonUnit使用${json-unit.regex}前缀来标志正则表达式。

assertJsonEquals("{\"test\": \"${json-unit.regex}[A-Z]+\"}",
    "{\"test\": \"ABCD\"}");

类型占位符Type placeholders

与Mockito中通过any-*方法来匹配类型而不是具体的值类似,JsonUnit也提供了这一功能。可以通过${json-unit.any-*}来匹配JSON对象中各个元素值的类型。

assertThatJson("{\"test\":\"value\"}")
    .isEqualTo("{test:'${json-unit.any-string}'}");
assertThatJson("{\"test\":true}")
    .isEqualTo("{\"test\":\"${json-unit.any-boolean}\"}");
assertThatJson("{\"test\":1.1}")
    .isEqualTo("{\"test\":\"${json-unit.any-number}\"}");

选项****Options

上述功能虽然非常贴心实用,不过需要对预期结果进行修改,按照忽略、正则等匹配需求,在预期结果的JSON中填入或者替换相应的${json-unit.*} 占位符。如果数据量很小的话,这项工作时还可以接受,如果是较大数据量的比对,那预期结果的编写或者生成后按照JsonUnit的断言格式进行修改就比较繁琐了。于是JsonUnit提供了Options功能,用户无需修改预期结果或者实际结果,在断言过程中可由JsonUnit根据用户给出的选项来处理忽略等操作。

TREATING_NULL_AS_ABSENT

通过该匹配项表示元素的值为null的话,则忽略该元素进行匹配。这样,在导出数据成JSON时,如果是Null值的数据不会写入JSON结果的话,那么源数据和序列化以后的JSON数据就能匹配一致了。

assertJsonEquals("{\"test\":{\"a\":1}}",
    "{\"test\":{\"a\":1, \"b\": null, \"c\": null}}",
    when(TREATING_NULL_AS_ABSENT));

IGNORING_ARRAY_ORDER

忽略数组中记录的顺序。在本小节的开头中,笔者提到了这一断言时需要处理的场景。通过这一选项,可以很好地实现。

assertJsonEquals("{\"test\":[1,2,3]}",
    "{\"test\":[3,2,1]}",
    when(IGNORING_ARRAY_ORDER));

IGNORING_EXTRA_ARRAY_ITEMS

忽略数组中多余的记录。这就实现了处理断言中数据记录包含关系的需求。

assertJsonEquals("{\"test\":[1,2,3]}",
    "{\"test\":[1,2,3,4]}",
    when(IGNORING_EXTRA_ARRAY_ITEMS));
assertJsonEquals("{\"test\":[1,2,3]}",
    "{\"test\":[5,5,4,4,3,3,2,2,1,1]}",
    when(IGNORING_EXTRA_ARRAY_ITEMS, IGNORING_ARRAY_ORDER));

忽略额外字段IGNORING_EXTRA_FIELDS

这类似数据库断言时,忽略表的某些列后再进行比较。

assertThatJson("{\"test\":{\"a\":1, \"b\":2, \"c\":3}}")
    .when(IGNORING_EXTRA_FIELDS)
    .isEqualTo("{\"test\":{\"b\":2}}");

忽略值IGNORE_VALUES
只比较数据类型、不比较具体的值。

assertJsonEquals("{\"test\":{\"a\":1,\"b\":2,\"c\":3}}",
    "{\"test\":{\"a\":3,\"b\":2,\"c\":1}}",
    when(IGNORING_VALUES));

节点和数组索引Node & Array indexing

在之前的断言案例中,有提到通过whenIgnoringPaths来忽略某些路径,这其实是使用了JsonNode的方式,通过指定树节点路径的方式来实现。

assertThatJson("{\"root\":{\"test\":1, \"ignored\": 1}}")
    .whenIgnoringPaths("root.ignored"))
    .isEqualTo("{\"root\":{\"test\":1}}");

这种方式除了在whenIgnoringPaths使用之外,JsonJunit还提供了node方法来提取测试用例所需要的JSON元素。此外,这个表达方式还支持数组的下标。如果下标是负数的话,如下例中的[-1],代表了数组中的最后一条记录。

assertThatJson("{\"root\":{\"test\":[1,2,3]}}")
    .node("root.test[-1]").isEqualTo(3);

数字比较Numerical comparison

JsonUnit在做数字比较时,遵循以下的方法:

· 首先比较双方的类型,如果类型不一致,则不相等。因此,1 不等于1.0(int 和float类型的区别).如果使用了Moshi这个JSON解析库的话,由于其将所有数字类型都转换成Doulble类型,所以上面的案例也就相等了。

  1. 浮点数进行精确比较

当然,也可以在比较时设置公差(tolerance)。如果公差设置为0,那么两个数学意义上相同的数字,即使是不同类型,它们的比较结果也是相等。

assertThatJson("{\"test\":1.00}").node("test").withTolerance(0).isEqualTo(1);

如果设置了一个非零的公差值,那么如果abs(a-b) < tolerance,则会认为双方相等。

assertThatJson("{\"test\":1.00001}").node("test").withTolerance(0.001).isEqualTo(1);

JsonUnit, 真香

barcode.jpg

相关文章

网友评论

      本文标题:还在用equals来做对象断言么?

      本文链接:https://www.haomeiwen.com/subject/ufleuhtx.html