如何为 JSON 模型构建测试数据生成器



我有一个微服务/应用程序,它接受JSON并转换为Java POJO,并在该应用程序中进一步处理。假设结构是:

{
"header": {
"msgTs": "2020-02-20T11:00:00"
}
"hazmat": {
"name": "a"
} 
// some other properties
}
public class Hazmat {
private String hazmatName;
}
public class POJO {
private Header header;
private Hazmat hazmet;
}

在这里,"危险品"可能存在或不存在。对于所有测试数据,标头和 msgT 的值无关紧要。因此,我们可以对值进行硬编码;

现在为了测试它,我想创建一个测试数据构建器。构建器将是这样的:

public class PojoBulider {
private String hazmatName;
public PojoBuilder withHamatName(String hazmatName) { this.hazmatname = hazmatName; return this; }
public String build() {
// generate test data from the captured inputs eg "hazmatName".    
}
}

为了生成测试数据,用户输入/特征文件列值将使用一些选定的属性(例如 HazmatName)。其余的可以硬编码。

现在的问题是我们如何对固定值(如msgTs)进行硬编码?我可以有一个模板 json:

{
"header": {
"msgTs": "2020-02-20T11:00:00"
}
"hazmat": {
"name": "%%hazmatName%%"
} 
}

然后有:

public void build() {
String templateJson; // load from file;
templateJson = templateJson.replaceAll("%%hazmatName%%", hazmatName);
return templateJson;
}

问题是 JSON 可以具有存在和不存在的属性。所以我不能有多个具有不同可能值的模板Json,即一个模板Json:

{
"header": {
"msgTs": "2020-02-20T11:00:00"
}
"hazmat": {
"name": "%%hazmatName%%"
} 
}

另一个模板 json:

{
"header": {
"msgTs": "2020-02-20T11:00:00"
}
"toy": {
"name": "%%toyName%%"
} 
}

我不能有一个模板 json,因为:

{
"header": {
"msgTs": "2020-02-20T11:00:00"
}
"toy": {
"name": "%%toyName%%"
}
"hazmat": {
"name": "%%hazmatName%%"
} 
}

因为不是测试数据将同时具有"玩具"和"危险品"部分。

所以我被困住了。我可能有多个模板 json,例如用于危险品、hazmat.json 的模板 json:

"hazmat": {
"name": "%%hazmatName%%"
} 

另一个用于玩具:

"toy": {
"name": "%%toyName%%"
}

并将它们组合在一起,例如:

public String build() {   
if (hazmatName != null) {
// load hazmatJson
// append to templateJson 
}
if (toyName != null) {
// load toyJson
// append to templateJson
}
return templateJson;
}

我想改用POJO:

public String build() [
Pojo pojo = new Pojo();
Hazmat hazmat = new Hazmat();
hazmat.setHazmatName(this.hazmatName);
pojo.setHazmat(hazmat);
return new ObjectMapper().writeValueAsString(pojo);
}

但问题是我如何填充不会对所有 JSON 更改的标头等值。我可以有另一个 json:

{
"header": { "msgTs": "..." } }

然后使用 ObjectMapper load 加载它:

public String build() {
Header header = new ObjectMapper().load("header.json");
pojo.setHeader(header);
}

是否有一种纯粹的基于 java 的方法,完全不使用任何 json 文件(即 header.json、toy.json、hazmat.json)? 在这种情况下,人们通常如何生成测试数据?

正如您评论说您更喜欢修改字段而不是基于键的映射时,我写了这个答案来解释在该约束下我会做什么(并不是说这是最好的解决方案,因为我会在映射中使用常量键)。

仍然假设您最终希望生成类似 JSON 的String作为测试候选的输入,我将创建一个将自身打印为 json 格式的类。请注意,我只测试了对象数组,而不是原始数组!列表、地图和集类型也不包括在内,但您可以根据需要自行添加它们。

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public abstract class JSON {
public static final Object UNDEFINED = new Object();
public String toJsonThrowRTE() {
try {
return toJsonString();
} catch (Throwable t) {
throw new RuntimeException("I found thiz. May I keep it? :)", t);
}
}
public String toJsonString() throws NoSuchMethodException, SecurityException,
IllegalArgumentException, IllegalAccessException, InvocationTargetException {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Field field : getAllDeclaredFields()) {
String name = field.getName();
// This prevents endless recursion.
if (name.startsWith("this")) {
continue;
}
Class<?> fieldType = field.getType();
if (fieldType.isPrimitive()) {
// No need for the first field to have a ',' before it.
if (first) {
first = false;
} else {
sb.append(",n");
}
// This is cumbersome, but I have no cleaner solution.
sb.append(name).append(": ");
switch (fieldType.getTypeName()) {
case "int":
sb.append(field.getInt(this));
break;
case "long":
sb.append(field.getLong(this));
break;
case "boolean":
sb.append(field.getBoolean(this));
break;
case "char":
sb.append("'" + field.getChar(this) + "'");
break;
case "double":
sb.append(field.getDouble(this));
break;
case "float":
sb.append(field.getFloat(this));
break;
case "byte":
sb.append(field.getByte(this));
break;
case "short":
sb.append(field.getShort(this));
break;
}
} else {
String value = valueToString(field.get(this));
if (value != null) {
// No need for the first field to have a ',' before it.
if (first) {
first = false;
} else {
sb.append(",n");
}
sb.append(name).append(": ").append(value);
}
}
}
return "{n" + indent(sb.toString()) + "n}";
}
public String valueToString(Object value) throws NoSuchMethodException, SecurityException,
IllegalArgumentException, IllegalAccessException, InvocationTargetException {
if (value == null) {
return "null";
} else if (value == UNDEFINED) {
return null;
} else if (value instanceof JSON) {
JSON childJson = (JSON) value;
return childJson.toJsonString();
} else if (value instanceof String) {
return """ + value + """;
} else if (value instanceof Character) {
return "'" + value + "'";
} else if (value.getClass().isArray()) {
Object[] array = (Object[]) value;
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Object o : array) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(valueToString(o));
}
return "[ " + sb.toString() + " ]";
}
// Everything else is just toString.
return value.toString();
}
public List<Field> getAllDeclaredFields() {
List<Field> allFields = new ArrayList<>();
// We start with the current class, and stop at JsonStringBuilder.
// After that, the fields are Object fields, and we don't want those.
Class<?> currentClass = getClass();
while (JSON.class.isAssignableFrom(currentClass)) {
// We want to order the fields from the most general to the most specific,
// so we must add the new fields at the start.
List<Field> declaredFields = Arrays.asList(currentClass.getDeclaredFields());
List<Field> childFields = allFields;
allFields = new ArrayList<>(childFields.size() + declaredFields.size());
allFields.addAll(declaredFields);
allFields.addAll(childFields);
currentClass = currentClass.getSuperclass();
}
return allFields;
}
public String indent(String toIndent) {
return toIndent.replaceAll("(?:^|(\n))", "$1t");
}
}

从此类派生的所有对象都将根据命令将自身打印为 json。这是我能在短时间内想出的最强大的功能,但我认为它应该可以。 您现在可以使用构造函数等创建任何 POJO,因此您可以通过创建默认对象然后按需添加所需的测试值来设置测试。不打印JSON.UNDEFINED的对象。 下面是如何使用它的示例:

@Test
public void test() {
JSON topLevel = new JSON() {
Object msgTs = "2020-02-20T11:00:00";
Object name = "a";
Object[] someArray = { 7, "String intermix", 'k', 5d };
Object iAmNull = null;
Object undefined = JSON.UNDEFINED;
Object someChild = new JSON() {
int someInt = 42;
char myChar = 'j';
};
};
System.out.println(topLevel.toJsonThrowRTE());
}

上面的"测试"会将这个String打印到控制台:

{
msgTs: "2020-02-20T11:00:00",
name: "a",
someArray: [ 7, "String intermix", 'k', 5.0 ],
iAmNull: null,
someChild: {
someInt: 42,
myChar: 'j'
}
}

现在,您只需将此字符串插入 Json 解析器或要测试的任何内容即可。这将是您的基本对象,您可以使用@Before@BeforeAll以相同的方式创建它,具体取决于您的 junit 版本。从那里,您可以修改每个测试的对象。请记住声明可能用作或不用作JSON.UNDEFINED的字段,以便您可以分配有用的值,但除非您设置它们,否则不会打印它们!

JSON message = new JSON() {
Object header = new JSON() {
String msgTs = "2020-02-20T11:00:00";
};
Object hazmet = JSON.UNDEFINED;
Object toy = JSON.UNDEFINED;
// ...
};

最新更新