refactor(接口测试): 优化JSONPath断言匹配机制,支持正则忽略数字精度匹配

--story=1012676 --user=赵勇 jsonpath 断言精度匹配优化 https://www.tapd.cn/55049933/s/1399645

Signed-off-by: fit2-zhao <yong.zhao@fit2cloud.com>
This commit is contained in:
fit2-zhao 2023-08-01 11:46:34 +08:00 committed by fit2-zhao
parent d0075a710d
commit 4822880d91
1 changed files with 51 additions and 67 deletions

View File

@ -43,7 +43,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
/** /**
* This is main class for JSONPath Assertion which verifies assertion on * This is main class for JSONPath Assertion which verifies assertion on
@ -64,7 +63,7 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
private static final boolean USE_JAVA_REGEX = !JMeterUtils.getPropDefault( private static final boolean USE_JAVA_REGEX = !JMeterUtils.getPropDefault(
"jmeter.regex.engine", "oro").equalsIgnoreCase("oro"); "jmeter.regex.engine", "oro").equalsIgnoreCase("oro");
private static ThreadLocal<DecimalFormat> decimalFormatter = private static final ThreadLocal<DecimalFormat> decimalFormatter =
ThreadLocal.withInitial(JSONPathAssertion::createDecimalFormat); ThreadLocal.withInitial(JSONPathAssertion::createDecimalFormat);
private static DecimalFormat createDecimalFormat() { private static DecimalFormat createDecimalFormat() {
@ -133,8 +132,7 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
private void doAssert(String jsonString) { private void doAssert(String jsonString) {
Object value = JsonPath.read(jsonString, getJsonPath()); Object value = JsonPath.read(jsonString, getJsonPath());
if (!isJsonValidationBool()) { if (!isJsonValidationBool()) {
if (value instanceof JSONArray) { if (value instanceof JSONArray arrayValue) {
JSONArray arrayValue = (JSONArray) value;
if (arrayValue.isEmpty() && !JsonPath.isPathDefinite(getJsonPath())) { if (arrayValue.isEmpty() && !JsonPath.isPathDefinite(getJsonPath())) {
throw new IllegalStateException("JSONPath is indefinite and the extracted Value is an empty Array." + throw new IllegalStateException("JSONPath is indefinite and the extracted Value is an empty Array." +
" Please use an assertion value, to be sure to get a correct result. " + getExpectedValue()); " Please use an assertion value, to be sure to get a correct result. " + getExpectedValue());
@ -148,8 +146,7 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
return; return;
} }
} else { } else {
if ((isExpectNull() && value == null) if ((isExpectNull() && value == null) || assertMatch(value)) {
|| isEquals(value)) {
return; return;
} }
} }
@ -160,30 +157,18 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
String msg = ""; String msg = "";
if (this.isUseRegex()) { if (this.isUseRegex()) {
msg = "Value expected to match regexp '%s', but it did not match: '%s'"; msg = "Value expected to match regexp '%s', but it did not match: '%s'";
} else if (StringUtils.isNotEmpty(getOption()) && !this.isEquals(value)) { } else if (StringUtils.isNotEmpty(getOption()) && !this.assertMatch(value)) {
switch (getOption()) { msg = switch (getOption()) {
case "CONTAINS": case "CONTAINS" -> "Value contains to be '%s', but found '%s'";
msg = "Value contains to be '%s', but found '%s'"; case "NOT_CONTAINS" -> "Value not contains to be '%s', but found '%s'";
break; case "EQUALS" -> "Value equals to be '%s', but found '%s'";
case "NOT_CONTAINS": case "NOT_EQUALS" -> "Value not equals to be '%s', but found '%s'";
msg = "Value not contains to be '%s', but found '%s'"; case "GT" -> "Value > '%s', but found '%s'";
break; case "LT" -> "Value < '%s', but found '%s'";
case "EQUALS": case "DOCUMENT" ->
msg = "Value equals to be '%s', but found '%s'"; DocumentUtils.documentMsg(this.getName(), value, this.getElementCondition(), decimalFormatter);
break; default -> msg;
case "NOT_EQUALS": };
msg = "Value not equals to be '%s', but found '%s'";
break;
case "GT":
msg = "Value > '%s', but found '%s'";
break;
case "LT":
msg = "Value < '%s', but found '%s'";
break;
case "DOCUMENT":
msg = DocumentUtils.documentMsg(this.getName(), value, this.getElementCondition(),decimalFormatter);
break;
}
} else { } else {
msg = "Value expected to be '%s', but found '%s'"; msg = "Value expected to be '%s', but found '%s'";
} }
@ -208,27 +193,27 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
} }
} }
if (isDocument) { if (isDocument) {
return isEquals(value); return assertMatch(value);
} }
for (Object subj : value.toArray()) { for (Object subj : value.toArray()) {
if (!StringUtils.equals(getOption(), "NOT_CONTAINS")) { if (!StringUtils.equals(getOption(), "NOT_CONTAINS")) {
if (subj == null && this.isExpectNull() || isEquals(subj)) { if (subj == null && this.isExpectNull() || assertMatch(subj)) {
return true; return true;
} }
} else { } else {
result.add(isEquals(subj)); result.add(assertMatch(subj));
} }
} }
if (CollectionUtils.isNotEmpty(result) && StringUtils.equals(getOption(), "NOT_CONTAINS")) { if (CollectionUtils.isNotEmpty(result) && StringUtils.equals(getOption(), "NOT_CONTAINS")) {
if (result.stream().filter(item -> item == true).collect(Collectors.toList()).size() == result.size()) { if (result.stream().filter(item -> item).toList().size() == result.size()) {
return true; return true;
} else { } else {
return false; return false;
} }
} }
return isEquals(value); return assertMatch(value);
} }
private boolean isGt(String v1, String v2) { private boolean isGt(String v1, String v2) {
@ -251,42 +236,41 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
} }
} }
private boolean isEquals(Object subj) { private boolean assertMatch(Object subj) {
String str = DocumentUtils.objectToString(subj, decimalFormatter);
if (isUseRegex()) { if (isUseRegex()) {
if (USE_JAVA_REGEX) { String expectedValue = getExpectedValue();
return JMeterUtils.compilePattern(getExpectedValue()).matcher(str).matches(); String resultValue;
if (subj instanceof BigDecimal) {
resultValue = String.valueOf(((BigDecimal) subj).doubleValue());
try {
Double.parseDouble(getExpectedValue());
expectedValue = String.valueOf(Double.parseDouble(getExpectedValue()));
} catch (Exception e) {
expectedValue = getExpectedValue();
}
} else { } else {
Pattern pattern = JMeterUtils.getPatternCache().getPattern(getExpectedValue()); resultValue = DocumentUtils.objectToString(subj, decimalFormatter);
return JMeterUtils.getMatcher().matches(str, pattern); }
if (USE_JAVA_REGEX) {
return JMeterUtils.compilePattern(expectedValue).matcher(resultValue).matches();
} else {
Pattern pattern = JMeterUtils.getPatternCache().getPattern(expectedValue);
return JMeterUtils.getMatcher().matches(resultValue, pattern);
} }
} else { } else {
String str = DocumentUtils.objectToString(subj, decimalFormatter);
if (StringUtils.isNotEmpty(getOption())) { if (StringUtils.isNotEmpty(getOption())) {
boolean refFlag = false; return switch (getOption()) {
switch (getOption()) { case "CONTAINS" -> str.contains(getExpectedValue());
case "CONTAINS": case "NOT_CONTAINS" -> !str.contains(getExpectedValue());
refFlag = str.contains(getExpectedValue()); case "EQUALS" -> valueEquals(str, getExpectedValue());
break; case "NOT_EQUALS" -> valueNotEquals(str, getExpectedValue());
case "NOT_CONTAINS": case "GT" -> isGt(str, getExpectedValue());
refFlag = !str.contains(getExpectedValue()); case "LT" -> isLt(str, getExpectedValue());
break; case "DOCUMENT" ->
case "EQUALS": DocumentUtils.documentChecked(subj, this.getElementCondition(), decimalFormatter);
refFlag = valueEquals(str, getExpectedValue()); default -> false;
break; };
case "NOT_EQUALS":
refFlag = valueNotEquals(str, getExpectedValue());
break;
case "GT":
refFlag = isGt(str, getExpectedValue());
break;
case "LT":
refFlag = isLt(str, getExpectedValue());
break;
case "DOCUMENT":
refFlag = DocumentUtils.documentChecked(subj, this.getElementCondition(), decimalFormatter);
break;
}
return refFlag;
} }
Object expected = JSONValue.parse(getExpectedValue()); Object expected = JSONValue.parse(getExpectedValue());
return Objects.equals(expected, subj); return Objects.equals(expected, subj);
@ -359,7 +343,7 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
} else if (subj instanceof Map) { } else if (subj instanceof Map) {
//noinspection unchecked //noinspection unchecked
str = new JSONObject((Map<String, ?>) subj).toJSONString(); str = new JSONObject((Map<String, ?>) subj).toJSONString();
} else if (subj instanceof Double || subj instanceof Float) { } else if (subj instanceof Double || subj instanceof Float || subj instanceof BigDecimal) {
str = decimalFormatter.get().format(subj); str = decimalFormatter.get().format(subj);
} else { } else {
str = subj.toString(); str = subj.toString();