Mock Annotation for Java 注解mock介绍


所有规则均参照 前端界比较流行的mock框架:
javascript Mock规则官网

背景

市面上已经有很多常用的mock框架,比如easy-mock,mockio, 等,但是生成的数据要不都是给前端使用的,要不就是在java层无法自定义扩展功能。
但是有个前辈已经做了比较好的项目(Mock.java,jmockdata),mockj。调查了很多框架,没有一款以Java注解方式,通过简单灵活的方式快速生成想要的数。

而且支持注解的不能很好地在注解上表达数据的关联,不支持注解的使用起来代码又过于冗余。并不能用于提供其他方面的学习,更有甚者,在比如数据开发/数仓领域为了测试代码逻辑问题,也会要求生成相关的数据用于检验代码的合理性。

因此,本项目的宗旨是以简单,易用(使用注解),方便的方式用于代码的自动生成。

项目目标

通过注解的方式,通过自定义规则生成指定想要的随机测试的数据。对于很多项目开发而言,测试数据能够以最快的速度进行项目的交付。同时通过测试数据,也能够很好地进行一些框架的快速学习和教育。因此,本项目的宗旨是,以最小的成本和灵活的配置来搭建一个快速测试的数据。

maven项目坐标

<dependency>
  <groupId>com.github.a524631266</groupId>
  <artifactId>jmock-core</artifactId>
  <version>1.1.1</version>
</dependency>

项目依赖

在核心业务逻辑不依赖于其他框架。不过本项目为了快速开发依赖了如下

  1. lombok 一款自动生成getter/setter/construct的工具。节约开发时间(不参与核心)
  2. log4j 日志框架,只是用来打印日志(不参与核心)
  3. findbugs google 开发的一款注解系统,这个只是在方法上加入注解,并没有做其他方面的作用(不参与核心)

how to use

生成一个pojo类。

如下,首先定义了一个父类pojo.可以支持java基本类型(包装类)、字符串、数组、list,日期类型

package com.zhangll.flink;

import com.zhangll.flink.annotation.BasicTokenInfo;
import lombok.Getter;
import lombok.ToString;

import java.util.*;

@Getter
@ToString
public class Father {

    @BasicTokenInfo(min = "10", max = "100")
    private int age;
    @BasicTokenInfo(min = "100", max = "1000")
    private Integer id;
    @BasicTokenInfo(min = "1000000", max = "10000000")
    private Long money;
    @BasicTokenInfo(min = "3", max = "5")
    private String name;

    @BasicTokenInfo(count = "1", value = {"@First @Middle @last"})
    private String firstName;

    @BasicTokenInfo(value = {"张三", "李四" ,"王五" , "@First @Middle @last"}, count = "1")
    private String innerName;

    @BasicTokenInfo(min = "10", max = "20")
    private String Address;
    @BasicTokenInfo(min = "1000", max = "2000", dmin = "3", dmax = "8")
    private double money_d;
    @BasicTokenInfo(min = "1000", max = "2000", dmin = "3", dmax = "8")
    private float money_f;
    private char char_1;
    private Character a = 'c';
    private Short wShort;

    @BasicTokenInfo(min = "1000", max = "2000")
    private short uShort;

    private java.sql.Date date;
    private java.sql.Time time;
    private java.sql.Timestamp timestamp;
    // 男为1 女为0
    private boolean sex;
    @ContainerTokenInfo(
            innerBasicType = @BasicTokenInfo(min = "4", max = "7")
    )
    private ArrayList<String> sonsNameList;
    private List<String> sonsNameList2;
    private List<Integer> sonsAgeList;
    private LinkedList<Double> sonsMoneyList;
    private LinkedList<Long> sonsLongList;

    @ContainerTokenInfo(
            innerBasicType =  @BasicTokenInfo(min = "15", max = "30")
    )
    private Set<String> sonsNameSet;

    private Son son;

    private String[] stringArr;
    private Double[] doubleWrapperArr;
    private double[] doubleNoWrapperArr;
    private int[] intNoWrapperArr;
    private Integer[] intWrapperArr;

    private char[] charNoWrapperArr;
    private Character[] charWrapperArr;
//
    @ContainerTokenInfo(
            innerPojoType =  @PojoTokenInfo(
                    {
                            @TokenMapping(field = "id", basicTokenInfo = @BasicTokenInfo(min = "1", max = "10"))
                    }
            ),
            innerBasicType = @BasicTokenInfo(min = "1233", max = "12324")
    )
    @BasicTokenInfo(min = "1", max = "2")
    private ArrayList<Son> sonslist;
    private Son[] sonlist2;
    // 正则表达式生成
    @BasicTokenInfo(value = {"/\\d{ 1, 3}  abcd\\/ \\d/ [a-bA-H1-4]{1,5}/"})
    private String regrex;
    private Date date2;
}

@ToString
public class Son {
    private String name;
    private int id;
    @FieldTokenType(min = "10", max = "20")
    private int age;
    
    private SonSon son;
}

main.java

使用方法 context 为mock的上下文,可以通过上下文的mock方法

AnnotationMockContext context = new AnnotationMockContext();
for (int i = 0; i < 2; i++) {
    Object mock = context.mock(Father.class);
    System.out.println(mock);
}

输出结果为

1. Father(age=97, id=749, money=7012716, name=令, firstName=夏 幸 祁, innerName=张三, Address=摇, money_d=1474.3962, money_f=1113.0878, char_1=Ȗ, a=ʂ, wShort=203, uShort=1717, date=2020-10-31, time=07:25:40, timestamp=2020-10-30 02:06:41.0, sex=false, sonsNameList=[箭, 专, 谦, 舌, 轮, 喷, 螺, 塔, 鲜, 避], sonsNameList2=[唯, 再, 杯, 锡, 燃, 宵, 匪, 三, 畜, 向], sonsAgeList=[517, 846, 653, 263, 667, 136, 867, 56, 378, 182], sonsMoneyList=[7.4, 2.2, 5.957, 9.9405, 8.41, 1.51, 1.89, 8.3, 7.774, 3.4809], sonsLongList=[287, 998, 743, 286, 737, 750, 388, 793, 273, 865], sonsNameSet=[着, 乔, 泄, 否, 仙, 循, 绍, 中, 蛾, 环], son=Son(name=浮, id=715, age=17, son=SonSon(name=伏, id=362, age=18)), stringArr=[拒, 瓣, 他, 烘, 熔, 竟, 据, 桌, 党, 化], doubleWrapperArr=[5.15, 3.323, 2.3334, 7.8, 3.4, 5.64, 3.805, 8.64, 2.808, 7.8388], doubleNoWrapperArr=[6.2, 7.04, 3.414, 4.79, 4.5917, 4.518, 1.0301, 2.82, 8.421, 8.361], intNoWrapperArr=[686, 621, 416, 538, 581, 253, 383, 845, 533, 522], intWrapperArr=[844, 20, 161, 981, 279, 682, 607, 707, 8, 111], charNoWrapperArr=[˛, Ƅ, Œ, ŕ, ̊, ͛, Ü, ĉ, ȍ, Ǫ], charWrapperArr=[q, ǃ, ȳ, ̗, ̗, Ϊ, ɭ, Z, Ȇ, ¶])

2. Father(age=96, id=616, money=7651289, name=倒, firstName=汪 暨 国, innerName=张三, Address=吗, money_d=1669.9100904, money_f=1670.301, char_1=Ə, a=˴, wShort=103, uShort=1942, date=2020-10-29, time=09:53:48, timestamp=2020-10-30 09:56:16.0, sex=true, sonsNameList=[夺, 画, 刑, 袋, 对, 端, 舌, 膨, 掩, 妄], sonsNameList2=[贵, 蛇, 罢, 剃, 另, 扯, 延, 削, 股, 穴], sonsAgeList=[377, 688, 492, 833, 572, 679, 261, 162, 743, 816], sonsMoneyList=[8.974, 2.8, 3.1843, 3.7751, 4.6, 3.069, 9.51, 8.473, 1.5364, 3.28], sonsLongList=[261, 604, 445, 731, 400, 593, 618, 627, 956, 146], sonsNameSet=[言, 攀, 窃, 米, 炉, 蕉, 孩, 配, 苏, 迟], son=Son(name=首, id=870, age=18, son=SonSon(name=剖, id=144, age=15)), stringArr=[型, 渠, 低, 胆, 杨, 魔, 桃, 扒, 晨, 因], doubleWrapperArr=[7.173, 3.519, 9.9, 3.018, 3.79, 8.825, 3.7, 6.542, 5.051, 5.8], doubleNoWrapperArr=[5.21, 8.61, 4.8, 8.813, 4.5921, 2.91, 6.5, 6.7, 7.361, 3.0755], intNoWrapperArr=[273, 61, 734, 572, 772, 871, 932, 266, 832, 625], intWrapperArr=[34, 205, 401, 223, 297, 21, 605, 945, 140, 812], charNoWrapperArr=[΃, Ü, ĭ, ͆, ʹ, ƥ, Ζ, ̂, c, ɸ], charWrapperArr=[Τ, Ǵ, ρ, Ȗ, Ɠ, ΀, ċ, ʘ, ², ͊])

功能介绍

案例地址

1. 接受多层pojo递归嵌套

@ToString
class Father{
    Son son;
}
@ToString
class Son {
    Son2 son2;
    private Date date;
}
@ToString
class Son2 {
    @BasicTokenInfo(min = "1" , max = "100")
    private int a;
}


...main(){
   AnnotationMockContext annotationMockContext = new AnnotationMockContext();
    for (int i = 0; i < 100; i++) {
        Object mock = annotationMockContext.mock(Father.class);
        System.out.println(mock);
    }
}

2. 接受正则表达式(支持基本类型/String/Date类型)

添加正则文法匹配功能 (has done)[使用Java原生的Regrex表达式]

input

 @BasicTokenInfo(value = {"/\\d{ 1, 3}  abcd\\/ \\d/ [a-bA-H1-4]{1,5}/" , "/[a-z][A-Z][0-9]/", "/\\w\\W\\s\\S\\d\\D/", "/\\d{5,10}/"})
    private String regrex;
    
    
@BasicTokenInfo(value = "/\\d{1,3}/")
    private int regrexInteger;
@BasicTokenInfo(value = "/\\d{1,3}\\.\\d{1,6}/")
private double regrexDouble;
@BasicTokenInfo(value = "/201[1-8]-0[1-8]-0[1-8]/")
    private Date dateRegrex;

@BasicTokenInfo(value = "/[a-zA-Z]/")
    private Character charRegrex;

result 会选取其中一个作为regrex

RegrexPojo(regrex=42283, regrexInteger=656, regrexDouble=476.8, dateRegrex=2014-04-03, charRegrex=n)
RegrexPojo(regrex=2028284986, regrexInteger=20, regrexDouble=27.74132, dateRegrex=2015-01-01, charRegrex=e)
RegrexPojo(regrex=1  abcd/ 4/ 4hb, regrexInteger=393, regrexDouble=334.27, dateRegrex=2013-03-02, charRegrex=w)

3. 时间函数

@ToString
class DatePojo{
// 表示最小2010-10-10日,最大2010-10-20
    @BasicTokenInfo(min = "2010-10-10" , max = "2010-10-20")
    Date date;

// 同上
 @BasicTokenInfo(min = "2010-10-10 00:10:20" , max = "2010-10-20 00:10:20")
    Timestamp timestamp;

    // 同上
    @BasicTokenInfo(min = "00:10:20" , max = "00:11:20")
    Time time;

// 步长为 2天
    @BasicTokenInfo(min = "2010-10-10" , max = "2010-10-20",step = "2")
    Date date2;

    // 步长为2秒
    @BasicTokenInfo(min = "2010-10-10 00:10:20" , max = "2010-10-20 00:10:20", step = "2")
    Timestamp timestamp2;

    // 步长为2秒
    @BasicTokenInfo(min = "00:10:20" , max = "00:11:20", step = "2")
    Time time2;
}

out put

DatePojo(date=2010-10-11, timestamp=2010-10-10 17:30:22.0, time=00:10:27, date2=2010-10-10, timestamp2=2010-10-10 00:10:20.0, time2=00:10:20)
DatePojo(date=2010-10-10, timestamp=2010-10-17 20:55:40.0, time=00:11:16, date2=2010-10-12, timestamp2=2010-10-10 00:10:22.0, time2=00:10:22)
DatePojo(date=2010-10-10, timestamp=2010-10-19 14:30:19.0, time=00:10:52, date2=2010-10-14, timestamp2=2010-10-10 00:10:24.0, time2=00:10:24)
DatePojo(date=2010-10-17, timestamp=2010-10-12 22:59:23.0, time=00:10:35, date2=2010-10-16, timestamp2=2010-10-10 00:10:26.0, time2=00:10:26)
....

4. 容器随机

class Array01 {
    //容器大小在2和3数量之间,其中内的基本类型的约束在
    @ContainerTokenInfo(innerBasicType = @BasicTokenInfo(min = "2", max = "3"))
    @BasicTokenInfo(min = "2", max = "3")
    List<String> list;
    @ContainerTokenInfo(innerBasicType = @BasicTokenInfo(min = "1", max = "10"))
    @BasicTokenInfo(min = "2", max = "3")
    ArrayList<Integer> arrayList;
    @ContainerTokenInfo(innerBasicType = @BasicTokenInfo(min = "1", max = "10"))
    @BasicTokenInfo(min = "2", max = "3")
    Set<Boolean> set;
    @ContainerTokenInfo(innerBasicType = @BasicTokenInfo(min = "1", max = "10"))
    @BasicTokenInfo(min = "2", max = "3")
    HashSet<Double> hashSet;
    @ContainerTokenInfo(innerBasicType = @BasicTokenInfo(min = "1", max = "10"))
    @BasicTokenInfo(min = "2", max = "3")
    LinkedList<Short> linkedList;

}

OutPut

Array01(list=[拿晚芝, 摩笛宋, 贪会信], arrayList=[1, 5], set=[true], hashSet=[6.1], linkedList=[7, 6, 2])
Array01(list=[键僻旋, 便培, 梨忙买], arrayList=[4, 10], set=[true], hashSet=[5.8, 3.1, 1.9], linkedList=[6, 8, 3])
Array01(list=[亏二馅, 落键滴], arrayList=[10, 8, 8], set=[true], hashSet=[9.3, 2.1], linkedList=[5, 8])
Array01(list=[降孕待, 虾蹲], arrayList=[4, 5, 6], set=[true], hashSet=[7.1, 8.8], linkedList=[10, 10, 6])
Array01(list=[眠岭, 宁酒], arrayList=[3, 6, 7], set=[false, true], hashSet=[8.6, 3.1], linkedList=[7, 4])

5. 基本数据类型检测方法

以int为例子

@ToString
class IntegerPojo{
    @BasicTokenInfo(min = "1000", max = "10000")
    private Integer int1;

    @BasicTokenInfo(value = {"1" , "10", "100"})
    private int int2;
}
  1. int1 表示为 最小为1000, 最大 10000的随机值
  2. int2 表示为 从 value中 1, 10 , 100 为随机值任一取一个值

同理其他基本数据类型. 不过比较特殊的是boolean类型

@BasicTokenInfo(min = "1" , max = "99")
private boolean bool3;

这里min和max指的是 boolean为true 的概率为 max/ (max + min) = 99%

6. 支持 以@为前缀的语义转换

比如 @First表示姓名 @Middle

 "@First @Middle @last"

7. 优先级

在pojo中可能会嵌套pojo,同时我们定义

class ListPojo{
 @ContainerTokenInfo(
            innerPojoType = @PojoTokenInfo(
            value = {
                    @TokenMapping(field = "bool3",
                            basicTokenInfo = @BasicTokenInfo(min = "1", max = "2"))
            })
    )
    @BasicTokenInfo(min ="1", max = "1")
    BooleanPojo[] booleanPojos;
}

class BooleanPojo{
    @BasicTokenInfo(min = "1" , max = "100")
    private boolean bool3;
}

可以想象,在容器类中定义的优先级是高于原始类的

因此 @BasicTokenInfo(min = “1”, max = “2”) 会覆盖 @BasicTokenInfo(min = “1” , max = “100”)规则

8. 1.1.0 新增内容

继承类使用

demo

内部类使用

demo innerClass

详细案例

请点击此处,欢迎start项目,请作者喝杯茶哈

框架支持的类型

支持普通class/内部class

后续可以添加一个

内置基本类型@BasicTokenInfo修饰的变量类型

分类类描述
Java包装类Integer.class
Java包装类Character.class
Java包装类Double.class
Java包装类Float.class
Java包装类Long.class
Java包装类Short.class
Java包装类Boolean.class
java基本类型int.class
java基本类型char.class
java基本类型double.class
java基本类型float.class
java基本类型long.class
java基本类型short.class
java基本类型boolean.class
字符串类型String.class
时间类型sql.Date.class
时间类型sql.Time.class
时间类型sql.Timestamp.class
枚举类型Enum.class

@ContainerTokenInfo修饰的类型

分类类描述
数组类型Array.class(int[]…,String[],Object[])
集合类型List.class[List子类或list都可以]
集合类型Set.class[Set/HashSet等等Set的子类]

Pojo类型

分类类描述
Pojo类型Object.class

注解使用方式

注解1: @BasicTokenInfo

用来表达每个字段内置基本类型的取值的约束条件

注解2: @ContainerTokenInfo

容器,(Set和List)类型的数据之间

mockContext(上下文)对象

mock上下文对象是用来构建mock对象的上下文.可以利用上下文,关联生成对象之间的关系

@ToString
class Step01 {
    @BasicTokenInfo(min = "5", max = "10", step = "2")
    private int increase;
}

main(){
    AnnotationMockContext annotationMockContext = new AnnotationMockContext();
        for (int i = 0; i < 100; i++) {
            Object mock = annotationMockContext.mock(Step01.class);
            System.out.println(mock);
        }
}

output

Step01(increase=5)
Step01(increase=7)
Step01(increase=9)
Step01(increase=5)
Step01(increase=7)
Step01(increase=9)
....

此时输出的随机对象是以5为其实开始点,并以步长2在 [min, max]依次轮询增加.

2.step功能还能在array或者list中使用

注意: 注解的表达能力有限

在开发的过程中,注解无法嵌套定义,会出现 cyclic annotation element type 编译错误

即,在使用注解的时候,一般表达的是一层含义,无法嵌套使用

不过在平时使用的时候,一般都是比较简单的pojo类来定义,因此也不会出现嵌套内嵌套的关系。

本项目的目的是基于满足最基本所需。

为什么不用map?

本人认为 map对象一般是可以直接转化为pojo的key value表达式就可以表达,按照语义上来说map数据结构是pojo的特殊表达方式。

如果大家有疑惑,可以发起issue,我会及时解答的。希望能够帮你在前进的路上减轻一些负担。

1.1.2 新增内容

  1. Enum 类型的随机生成
    查看地址

  2. 开放支持用户自定义规则,详情请看

自定义Enum扩展类

注册方式

后续进展

会支持更多@前缀语义

有任何需求的同学可以参与进去哦,也欢迎提bug和随机需求

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值