总结:开发的原因是一个非常小的类库,通过代码生成提供高性能的反射处理,并自动提供对字段的访问类。 访问类使用字节码操作而不是反射技术,因此速度非常快。
开发原因
ReflectASM 是一个非常小的 Java 类库,它通过代码生成提供高性能的反射处理,并自动提供 get/set 字段的访问类。 Access 类使用字节码操作而不是 Java Reflective 技术,因此速度非常快。
在单元测试时,需要模拟并创建数据来测试代码中是否存在明显的异常(字段原因、空指针)。 除了自己随机写一些测试数据之外,我们还使用了javafaker。 运行“真实数据”模拟肯定可以减少代码大小。 早期简单的测试,数据的准确性不高。 使用javafaker创建每个字段仍然需要大量代码。 于是,此时,基于javafaker和ReflectASM的工具FakeDataMaker诞生了。 数据“混乱”填充的测试工具
UML图
源码分析 AbstractFakeDataMaker
该类是整个工具的基类,对各种类型的参数Define有抽象方法。 本质上,它包括常用的基类,以及 Date、Boolean 和 BigDecimal 类型。
这里的输入参数是String fieldName。 虽然这个工具可以随机生成数据,但有些领域仍然需要专门化。 在处理过程中,您可以重写这些方法以根据收到的字段名称执行数据的自定义输入。
FakeDataMakeronlyFieldOne = new FakeDataMaker(){@Override protected String makeString(String fieldName) { if("data3".equals(fieldName)){ return "zjjdjd" 返回 super.makeString(fieldName);
这样就可以将data3字段固定为“zjjdjd”并进行自定义。
FakeDataMaker
这是该工具中最重要的类。 继承AbstractFakeDataMaker方法,实现几个创建数据的方法。 该类目前可以实现两个功能。 一个用于填充数据,另一个用于构造空值。
填充数据
要获取填充数据的对象,请调用makeData方法。 有两个 makeData 方法。 Custom Faker的默认字符集是中文,也支持自定义Define字符集。
public Final Object makeData(Class testClass) { return makeData(testClass, Locale.CHINA);}
许多缓存是: 在makeData方法中使用,以加速数据扫描和生成。
// 构造对象 ConstructorAccessconstructorAccess = CONSTRUCTOR_ACCESS_MAP.get(testClass);if (constructorAccess == null) {constructorAccess = ConstructorAccess.get(testClass);rAccess);} ·····//类方法的缓存 MethodAccess testAccess = METHOD_ACCESS_MAP.get(testClass); if (testAccess == null) { testAccess = MethodAccess.get(testClass); ; }・・・・・ //缓存列表 field = FIELDS_MAP.get(testClass); if (field == null) { field = getAllFields(testClass); }・・・. .. //获取索引缓存对应get方法的整数 set_index = INDEX_MAP.get(get_key); if (set_index == null) { set_index = testAccess.getIndex(GET_METHOD + StringUtils.capitalize(field.getName()) ); .put(get_key, set_index);}
这里我们使用 ReflectASM 工具 ConstructorAccess 和 MethodAccess 来方便对象构造并获取用于赋值的方法句柄。 为什么这里要介绍ReflectASM工具呢?因为这项工作的开发涉及到使用R实现一个字段映射功能。在我们的测试过程中,我们趁热打铁使用了ReflectASM工具,因为使用ReflectASM+注解的方式为我们节省了大量的代码。 然而,主要原因是 ReflectASM 工具更容易使用。
因为同事第一次使用FakeDataMaker工具。 因为我们直接使用基类进行赋值,报错了,所以我们对收到的基类赋值做了特殊处理。
//构造基本类型参数 if (baseClass(testClass)) { if (WARNING_FLAG ) { System.err.println("生成基类数据,直接使用Java-faker WARNING_FLAG = false; returns baseClassValue(testClass,新对象())。 }
虽然不推荐给基类赋值,但是使用Java Faker还是很有用的。 为了避免异常,请输入主基类数据。
FakeDataMaker util = new FakeDataMaker();Integer testData = (Integer) util.makeData(Integer.class);System .out.println(testData); 要生成基本类数据,请使用 Java-faker 我们推荐使用它直接地。 87
baseClassValue 方法仍然调用内部赋值方法。
为了接收类,存在一个内部类。 现在我们需要调用 setInnerFlag 方法来启用对内部类的支持。
if (INNER_FLAG) { Boolean baseValue = BASE_MAP.get(get_key); if (基值)== null) {baseValue = !baseClass(field.getType()) && !field.getType().equals(testClass) && Modifier.toString(field.getType().getModifiers()).contains("static") ; BASE_MAP.put(get_key,baseValue); if (baseValue) { ObjectbaseClassValue = makeData(field.getType(), locale); testAccess.invoke(testObject, set_index, baseClassValue); }}
内部类数据填充为 ,实际上是对 makeData 的递归调用。
接下来就是核心部分,根据字段的数据类型进行赋值操作。 这里使用了重写的AbstractFakeDataMaker定义的一些值创建方法。
p> if (field.getType() == String.class) { testAccess.invoke(testObject, set_index, makeString(field.getName()) ));}if (field.getType() == Integer .class || field .getType() == int.class) { testAccess.invoke(testObject, set_index, makeInteger(field.getName()));}if (field.getType() == Float.class || field.getType() == float.class) { testAccess.invoke(testObject, set_index, makeFloat(field.getName()));}if (field.getType() == Double.class || field.getType() = = double.class) { testAccess.invoke(testObject, set_index, makeDouble(field.getName()));}if (field.getType() == Long.class || field.getType() == long.class) { testAccess.invoke(testObject, set_index, makeLong(field.getName()));}if (field.getType() == Date.class) { testAccess.invoke(testObject, set_index, makeDate(field.getName()) );}if (field.getType() == Boolean.class || field.getType() == boolean.class) { testAccess.invoke(testObject, set_index, makeBoolean(field.getName()));}if ( field.getType() == BigDecimal.class) { testAccess.invoke(testObject, set_index, makeBigDecimal(field.getName()));}
字符类型当前填充有伪造者的名称类字段类型
@Overrideprotected String makeString(String fieldName) { return faker.name().fullName();} @Overrideprotected Integer makeInteger(String fieldName) { return faker.number().numberBetween(1, 100) ;}@Overrideprotected Float makeFloat(String fieldName) { Double randomDouble = faker.number().randomDouble(2, 1, 100); return randomDouble.floatValue();}@Overrideprotected Double makeDouble(String fieldName) { return faker.number ().randomDouble(2, 1, 100);}@Overrideprotected Long makeLong(String fieldName) { Double randomDouble = faker.number( ).randomDouble(2, 1, 100); returnrandomDouble.longValue();}@Overrideprotected Date makeDate(String fieldName) { return faker.date().birthday();}@Overrideprotected Boolean makeBoolean(String fieldName) { if (faker.number().numberBetween(0, 2) == 1) { return Boolean.TRUE; } return Boolean.FALSE;}@Overrideprotected BigDecimal makeBigDecimal(String fieldName) { Double randomDouble = faker.number().randomDouble(2, 1, 1000); return new BigDecimal(String.valueOf(randomDouble));}
由于这是功能测试的简单数据输入,默认的方法在数据精度上还是存在较大偏差。 因此,在实际使用过程中,你大多会重写各种创建值的方法,例如返回uuid、字符时间、数字等的ID字段。
FakeDataMaker stringOnlyOne = new FakeDataMaker() { @Override protected String makeString(String fieldName) { if ("id".equals(fieldName)) { return "1231231231231321" 返回 super.makeString(fieldName); } }}; 构造空值
这也是我同事的代码中出现的问题。 例如,我想到的一个积极的解决方案是统计 VO。 有许多统计数字属性。 如果找到结果,则会分配一个值。 如果没有结果,则默认为 0。 使用赋值方法时,必须对所有属性进行赋值。 赋值默认值0或者调用构造函数方法赋值。 这两种方法都涉及大量代码,而且不是很简洁。 特别是使用构造方法时,输入参数较多。 使用FakeDataMaker 可以用一行代码完成空值对象赋值。
TestData testData1 = (TestData) FakeDataMaker.initEmptyObject(TestData.class);
可以看到实际效果。 字符类型默认为“”,数字默认为0或0.0,时间默认为当前时间【传输外部链接图片失败。 源站点可能具有到达保护功能。 由于机制原因,我们建议保存图片并直接上传(img-F6DTyUik-1633882390009)(https://www .akura.ren/upload/2021/10/%E6%88% AA%E5 %) B1%8F2021-10-11%20%E4%B8%8A%E5%8D%8812.00.38- 0dbbc7a502de44dba717a274079694e4.png)]
默认情况下,仅构造空支持内部类值的填充。 您可以通过调用 initEmptyObject(Class testClass, Boolean innerFlag) 来关闭内部类嵌入。
在initEmptyObject方法内部,重写了FakeDataMaker方法,并用空值重写了值创建方法。 FakeDataMaker内部会缓存FakeDataMaker对象,方便重复调用。
if (EMPTY_OBJECT_MAKER == null || !EMPTY_OBJECT_MAKER.INNER_FLAG.equals (innerFlag)) { EMPTY_OBJECT_MAKER = new FakeDataMaker() { @Override protected String makeString(String fieldName) { back ""; }@Override protected Integer makeInteger(String fieldName) { return 0; @Override protected Float makeFloat(String fieldName) { return 0F; @Override protected Double makeDouble(String fieldName) { return 0D } @Override protected Long makeLong(String fieldName ) { return 0L; } @Override protected Date makeDate(String fieldName) { //这里没有设置缓存 return new Date(); @Override protected Boolean makeBoolean(String fieldName) { return Boolean.TRUE; ) { 返回 BigDecimal.ZERO; } };InnerFlag(innerFlag);}return EMPTY_OBJECT_MAKER.makeData(testClass); 源码地址
https://github.com/liuhao192/FakerTestDataMakeUtil
评论前必须登录!
注册