资讯专栏INFORMATION COLUMN

项目工具:两行代码快速生成测试的数据的FakeDataMaker

X_AirDu / 2599人阅读

摘要:开发原因是一个非常小的类库,通过代码生成来提供高性能的反射处理,自动为字段提供访问类,访问类使用字节码操作而不是的反射技术,因此非常快。

开发原因

ReflectASM 是一个非常小的 Java 类库,通过代码生成来提供高性能的反射处理,自动为 get/set 字段提供访问类,访问类使用字节码操作而不是 Java 的反射技术,因此非常快。

在单元测试的时,需要模拟制造一些数据去测试我们代码会不会出现明显的异常(字段导致、空指针),除了自己乱编写一些测试数据以外,也会实用javafaker进行“真实数据”模拟,当然可以减少我们的代码量。如果,只是前期的简单测试,对数据的正确性并不高,使用javafaker对每个字段都进行编造,代码量还是不少,于是,这个时候,FakeDataMaker就这么诞生了,一个基于javafaker和ReflectASM工具“乱”填充数据的测试工具

UML图

源码分析

AbstractFakeDataMaker

这个类是整个工具的一个基类,定义了各种类型的参数的抽象方法,基本上包括常用的基类,又加上了Date、Boolean、BigDecimal类型

三种类型,这里入参为String fieldName,虽然工具可以乱生成数据,但是,要是有些字段还是需要特殊化的处理,便可以重写这些方法,根据传入的字段名称,进行数据自定义填充,比如

FakeDataMaker onlyFieldOne = new FakeDataMaker(){        @Override        protected String makeString(String fieldName) {            if("data3".equals(fieldName)){                return "zjjdjd";            }            return super.makeString(fieldName);        }    };

这样便可以自定义data3字段,固定为“zjjdjd”

FakeDataMaker

这是这个工具中最重要的类,继承了AbstractFakeDataMaker方法,并实现了几个造数据的方法,目前这个类可以实现两个功能,一个是填充数据,一个是构造空值的对象

填充数据

调用makeData方法便可以得到一个填充完数据的对象,有两个makeData方法,默认自定义faker的字符集为中文,也支持自定义字符集

public final Object makeData(Class testClass) {    return makeData(testClass, Locale.CHINA);}

makeData方法中使用了很多缓存,便于遍历生成数据的速度

//构建对象ConstructorAccess constructorAccess = CONSTRUCTOR_ACCESS_MAP.get(testClass);if (constructorAccess == null) {    constructorAccess = ConstructorAccess.get(testClass);    CONSTRUCTOR_ACCESS_MAP.put(testClass, constructorAccess);}·····//类方法的缓存MethodAccess testAccess = METHOD_ACCESS_MAP.get(testClass);        if (testAccess == null) {            testAccess = MethodAccess.get(testClass);            METHOD_ACCESS_MAP.put(testClass, testAccess);        }····· //字段的缓存 List<Field> fields = FIELDS_MAP.get(testClass);        if (fields == null) {            fields = getAllFields(testClass);            FIELDS_MAP.put(testClass, fields);        }·····  //获取到对应的get方法的下标缓存 Integer set_index = INDEX_MAP.get(get_key);            if (set_index == null) {                set_index = testAccess.getIndex(GET_METHOD + StringUtils.capitalize(field.getName()));                INDEX_MAP.put(get_key, set_index);}

这里使用了ReflectASM工具的ConstructorAccess、MethodAccess,方便构建对象和获取到方法的句柄,从而进行赋值,这里为什么会引入ReflectASM工具,因为,我在工作开发中涉及到一个字段映射的功能实现,使用ReflectASM+注解的方法可以减少大量代码,所以在测试过程中,乘热打铁就ReflectASM工具,不过,主要还是ReflectASM工具真的比较好用

由于我同事第一次使用FakeDataMaker工具就直接使用了基类进行了赋值,然后就报错了,所以对于传入基类的赋值做了点特殊处理

//基础类型的参数构建        if (baseClass(testClass)) {            if (WARNING_FLAG) {                System.err.println("建议直接使用Java—faker生成基础类数据");                WARNING_FLAG = false;            }            return baseClassValue(testClass, new Object());        }

虽然,不建议对基类进行赋值,使用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 (baseValue == null) {        baseValue = !baseClass(field.getType()) && !field.getType().equals(testClass)                && Modifier.toString(field.getType().getModifiers()).contains("static");        BASE_MAP.put(get_key, baseValue);    }    if (baseValue) {        Object baseClassValue = makeData(field.getType(), locale);        testAccess.invoke(testObject, set_index, baseClassValue);    }}

内部类的数据填充,其实是一个递归调用makeData

接下来是核心部分,根据字段的数据类型,进行赋值操作,这里就使用到了重写的AbstractFakeDataMaker**定义的几个造值方法

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()));}

目前,字符型,填充的是,faker的name类型的字段

@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);    return randomDouble.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));}

由于只是简单的功能测试的数据填充,在数据的准确性上默认方法还是有很大的偏差的,所以,我在实际的使用过程中,大部分还是重写了造值的各种方法,比如ID字段,就返回uuid,字符的时间、数字等

FakeDataMaker stringOnlyOne = new FakeDataMaker() {    @Override    protected String makeString(String fieldName) {       if ("id".equals(fieldName)) {                    return "1231231231231321";                }      return super.makeString(fieldName);    }};

构造空值

这也是在我同事的代码中遇到的一个问题,从而想到的一个积极方案,比如一个统计类的VO,会有很多统计的数值属性,能查到结果的就会赋值,没有结果的就默认0,如果使用赋值方法则需要把全部的属性都要赋值默认值0,或者调用构造方法赋值,两种方法都会有大量的代码,不太简洁,尤其使用构造方法,会有很多的入参,使用FakeDataMaker一句代码就可以完成空值对象的赋值

TestData testData1 = (TestData)  FakeDataMaker.initEmptyObject(TestData.class);

实际效果可以看到,字符类型默认为“”,数字值则是0或者0.0,时间则默认为当前时间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F6DTyUik-1633882390009)(https://www.kura.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) {            return "";        }        @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;        }        @Override        protected BigDecimal makeBigDecimal(String fieldName) {            return BigDecimal.ZERO;        }    };    EMPTY_OBJECT_MAKER.setInnerFlag(innerFlag);}return EMPTY_OBJECT_MAKER.makeData(testClass);

源码地址

https://github.com/liuhao192/FakerTestDataMakeUtil

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/122269.html

相关文章

  • Node.js运行原理、高并发性能测试对比及生态圈汇总

    摘要:模式,单实例多进程,常用于多语言混编,比如等,不支持端口复用,需要自己做应用的端口分配和负载均衡的子进程业务代码。就是我们需要一个调度者,保证所有后端服务器都将性能充分发挥,从而保持服务器集群的整体性能最优,这就是负载均衡。 showImg(https://segmentfault.com/img/remote/1460000019425391?w=1440&h=1080); Nod...

    kamushin233 评论0 收藏0
  • Node.js运行原理、高并发性能测试对比及生态圈汇总

    摘要:模式,单实例多进程,常用于多语言混编,比如等,不支持端口复用,需要自己做应用的端口分配和负载均衡的子进程业务代码。就是我们需要一个调度者,保证所有后端服务器都将性能充分发挥,从而保持服务器集群的整体性能最优,这就是负载均衡。 showImg(https://segmentfault.com/img/remote/1460000019425391?w=1440&h=1080); Nod...

    khs1994 评论0 收藏0
  • Node.js运行原理、高并发性能测试对比及生态圈汇总

    摘要:模式,单实例多进程,常用于多语言混编,比如等,不支持端口复用,需要自己做应用的端口分配和负载均衡的子进程业务代码。就是我们需要一个调度者,保证所有后端服务器都将性能充分发挥,从而保持服务器集群的整体性能最优,这就是负载均衡。 showImg(https://segmentfault.com/img/remote/1460000019425391?w=1440&h=1080); Nod...

    BDEEFE 评论0 收藏0
  • Node.js运行原理、高并发性能测试对比及生态圈汇总

    摘要:模式,单实例多进程,常用于多语言混编,比如等,不支持端口复用,需要自己做应用的端口分配和负载均衡的子进程业务代码。就是我们需要一个调度者,保证所有后端服务器都将性能充分发挥,从而保持服务器集群的整体性能最优,这就是负载均衡。 showImg(https://segmentfault.com/img/remote/1460000019425391?w=1440&h=1080); Nod...

    TesterHome 评论0 收藏0
  • 走进机器学习世界之TensorFlow.js快速上手

    摘要:具体解释就是机器学习是实现人工智能的一种手段深度学习是实现机器学习的一种技术今天我们要介绍的是由的团队发布一款机器学习框架,基于已经停止更新。所以对于前端来说,是走进机器学习世界最便捷的路径了。 前言 近两年人工智能,机器学习等各种概念漫天飞舞,那人工智能,机器学习,深度学习这些名词之间是什么关系呢? 如果用三个同心圆来解释的话,人工智能是最大的圆,机器学习是中间的圆,深度学习是最小的...

    stonezhu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<