给新写的项目生成一些业务数据

324 阅读9分钟

1、需求

小王公司做了一个toB的系统,做出来后直接卖给企业客户。现在东西做出来了, 并且已经运行在自己的服务器上了,但是因为没有真正的用户使用, 所以这个系统很多功能都没法给客户演示, 老板让小王给这个系统造一点数据出来。

2、实现方式

小王拿到了这个需求,一开始他想着让自己下面的人每天用用这个系统,又能满足需求,还能测测问题。 然后公司就有一个人专门负责这个事情。一开始还好,后面问题来了,造的数据太假了,一下子就能看出来是假数据。而且数据量太少。“人工这么造肯定是不行了”, 小王心里想着。

后面小王找到公司小明,让他帮忙做这个事情。小明说:“这个好办,直接写个工具调接口造就好了”,然后半个月后总算可以造数据了。小王非常高兴。 但随着产品的迭代开发,功能也越来越多,新的功能也要造数据,“每次都给新的功能写一套接口调用太麻烦了”,小明说,“要写一个通用的,小王也能自己造数据的工具出来, 只要知道数据库中数据表之间的关联关系,往数据库里面直接插入数据就好了”。

上面的例子是这个工具的由来,例子中的小明就是我,为了减少这无休无止的代码维护,编写了这个工具。

3、举例说明

假设业务场景为学生可以在系统内选择课程报名。数据库中的业务相关联的数据表有:学生表、课程表、学生选课表

3.1、数据准备

数据表建表语句如下

---学生表---
CREATE TABLE `stu` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `name` varchar(20) NOT NULL COMMENT '学生名字',
  `school` varchar(20) NOT NULL COMMENT '学校名字',
  `nickname` varchar(20) NOT NULL COMMENT '学生小名',
  `age` int(11) NOT NULL COMMENT '学生年龄',
  `class_num` int(11) NOT NULL COMMENT '班级人数',
  `score` decimal(4,2) NOT NULL COMMENT '成绩',
  `phone` int(20) NOT NULL COMMENT '电话号码',
  `email` varchar(64) DEFAULT NULL COMMENT '家庭网络邮箱',
  `ip` varchar(32) DEFAULT NULL COMMENT 'IP地址',
  `address` text COMMENT '家庭地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--课程表--
CREATE TABLE `course` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `name` varchar(20) NOT NULL COMMENT '课程名称',
  `grade` varchar(20) NOT NULL COMMENT '年级',
  `teacher` varchar(20) NOT NULL COMMENT '授课老师',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--选课表--
CREATE TABLE `choice_course` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `stu_id` varchar(20) NOT NULL COMMENT '学生ID',
  `course_id` varchar(20) NOT NULL COMMENT '课程ID',
  `time` varchar(20) NOT NULL COMMENT '上课时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

我们要往数据库中插入模拟数据,需要先对数据库字段进行规格描述。dbfaker通过yaml文件来描述字段的生成规则。 通过业界知名的假数据生成库faker来生成,支持自定义生成规则。

生成字段描述的yaml文件可以通过内置的转换工具,将数据库中的建表语句一键转换为yaml文件;使用方式如下:

usage: table2yml [-h] [--connect [CONNECT]] [--table_names [TABLE_NAMES]]
                 [--sql_file [SQL_FILE]] [--output [OUTPUT]]
                 [type]

数据库表转数据生成yaml文件格式工具

positional arguments:
  type                  数据来源,table_name: 通过输入表名与数据库链接方式,在数据库中获取数据库建表语句;
                        table_statement: 指定数据库建表语句的sql文件路径

optional arguments:
  -h, --help            show this help message and exit
  --connect [CONNECT]   数据库连接语法,例如:mysql+mysqldb://pdmsadmin:system001@cpcs.ho
                        melabs.in/pdms_hospital
  --table_names [TABLE_NAMES]
                        数据库表,多个表以“,”分割
  --sql_file [SQL_FILE]
                        数据库建表语句的sql文件路径
  --output [OUTPUT]     输出文件名,默认为数据库表名+meta.yml

3.2、数据库表转换为yaml字段描述文件

1、在可以连接数据库的情况下直接指定数据库连接和要转换的表名来输出yaml文件

table2yml table_name --table_names stu,course,choice_course --connect mysql+mysqldb://[dbuser]:[dbpassword]@[dbhost]/[dbname] --output test.yml

2、在不能直接连接数据库,但有建表语句sql文件时可以通过下面的方法来输出yaml文件

table2yml table_statement --sql_file test.sql --output test.yml

输出的yaml文件如下:

# 请完善此文件中每个字段的生成规则
# 规则说明:
# package: 动态导包,在下方字段使用了jinja2模板且在模板语法中使用了非Python基础库时需要在此动态声明导入需要的包;使用示例:
# package:
#  - datetime  # 引入datetime包,可在后续的jinja2模板中使用
#  - os

# env: 可在此处预生成环境变量,给下方字段生成时引用;描述方式如下:
# env:
#  name:  # 全局变量名称
#    engine: eq  # 生成规则方法,与下面的字段生成方法一样
#    rule:  # "engine"方法中接收的参数
#      value: 'test'  

# tables: 该字段描述了表字段的生成规则,需要填写数据库字段中的engine与rule字段,为空时数据库字段也为空;示例(给数据库中t_sys_user表中age字段生成从40到80的随机数):
# tables:
# - table: t_sys_user
#   comment: 用户表
#   columns:
#   - column: age  # 数据库中字段名
#     comment: '年龄'  # 字段备注信息
#     engine: randint  # 生成字段值调用的方法,必须是faker库中存在或者自行注册到faker库中的方法
#     rule: 
#        value: [40, 80]  # 上述方法中接收到的参数
       
# extraction: 该字段描述了需要从生成字段中提取哪些变量来返回,写自动化测试用例时可能会用到;举例:
# extraction:
#   user_name:
#     value: '{{ t_sys_user.name }}'  # 返回上面生成的用户姓名
#     default: '测试用户'  # 可指定默认值,在上述字段不存在或者为空时返回默认值
#   user_id:
#     value: '{{ t_sys_user.id }}'  


package: []
env: {}
tables:
- table: stu
  comment: null
  columns:
  - column: id
    comment: 自增id
    engine: null
    rule: null
  - column: name
    comment: 学生名字
    engine: null
    rule: null
  - column: school
    comment: 学校名字
    engine: null
    rule: null
  - column: nickname
    comment: 学生小名
    engine: null
    rule: null
  - column: age
    comment: 学生年龄
    engine: null
    rule: null
  - column: class_num
    comment: 班级人数
    engine: null
    rule: null
  - column: score
    comment: 成绩
    engine: null
    rule: null
  - column: phone
    comment: 电话号码
    engine: null
    rule: null
  - column: email
    comment: 家庭网络邮箱
    engine: null
    rule: null
  - column: ip
    comment: IP地址
    engine: null
    rule: null
  - column: address
    comment: 家庭地址
    engine: null
    rule: null
- table: course
  comment: null
  columns:
  - column: id
    comment: 自增id
    engine: null
    rule: null
  - column: name
    comment: 课程名称
    engine: null
    rule: null
  - column: grade
    comment: 年级
    engine: null
    rule: null
  - column: teacher
    comment: 授课老师
    engine: null
    rule: null
- table: choice_course
  comment: null
  columns:
  - column: id
    comment: 自增id
    engine: null
    rule: null
  - column: stu_id
    comment: 学生ID
    engine: null
    rule: null
  - column: course_id
    comment: 课程ID
    engine: null
    rule: null
  - column: time
    comment: 上课时间
    engine: null
    rule: null
extraction: {}

3.3、描述字段生成规则

package: []
env: {}
tables:
- table: stu
  comment: null
  columns:
  - column: id
    comment: 自增id
    engine: uuid  # dbfaker内置方法,生成32为uuid字符串
    rule: null
  - column: name
    comment: 学生名字
    engine: name  # faker标准库方法;
    rule: null
  - column: school
    comment: 学校名字
    engine: eq  # dbfaker内置方法;直接返回给定value
    rule: 
      value: 中国家里蹲小学
  - column: nickname
    comment: 学生姓名拼音
    engine: hans2pinyin  # dbfaker内置方法;给定汉字返回拼音
    rule: 
      hans: '{{ stu.name }}'  # 通过jinja2模板直接调用上面的学生姓名
      style: 'A'  # style: A:全拼; F:首字母
  - column: age
    comment: 学生年龄
    engine: randint  # 从给定范围数字中随机取一个整数
    rule: 
      value: [10, 15]
  - column: score
    comment: 成绩
    engine: weights_choice  # 带权重的随机返回
    rule: 
      - value: [A,B]  # 这里意思是返回A或者B的权重是30%,返回C、D、E的权重是70%
        weight: 0.3
      - value: [C,D,E]
        weight: 0.7 
  - column: phone
    comment: 电话号码
    engine: phone_number # faker标准库方法;
    rule: null
  - column: email
    comment: 家庭网络邮箱
    engine: email # faker标准库方法;
    rule: null
  - column: address
    comment: 家庭地址
    engine: address # faker标准库方法;
    rule: null
- table: course
  comment: null
  columns:
  - column: id
    comment: 自增id
    engine: uuid
    rule: null
  - column: name
    comment: 课程名称
    engine: choice
    rule: 
      value: [语文, 数学, 英语, 化学, 物理]
  - column: grade
    comment: 年级
    engine: choice
    rule: 
      value: [一年级,二年级,三年级,四年级,五年级,六年级]
  - column: teacher
    comment: 授课老师
    engine: name
    rule: null
- table: choice_course
  comment: null
  columns:
  - column: id
    comment: 自增id
    engine: uuid
    rule: null
  - column: stu_id
    comment: 学生ID
    engine: eq
    rule: 
      value: '{{ stu.id }}'
  - column: course_id
    comment: 课程ID
    engine: eq
    rule: 
      value: '{{ course.id }}'
  - column: time
    comment: 上课时间
    engine: now
    rule: 
      format: '%Y-%m-%d %H:%M:%S'
extraction: {}

3.4、生成sql


dbfaker ./test.yml -n 2 -p -i --connect --connect mysql+mysqldb://[dbuser]:[dbpassword]@[dbhost]/[dbname] -o test.sql
# 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:00<00:00, 170.90条/s]
# INSERT `stu` (`id`,`name`,`school`,`nickname`,`age`,`score`,`phone`,`email`,`address`) VALUES ('22d4d964c84b4f0d8b507494a150b4f2','王娜','中国家里蹲小学','wangna',14,'E','18251993941','li61@hotmail.com','陕D座 227726');
# INSERT `course` (`id`,`name`,`grade`,`teacher`) VALUES ('4358177385ac49b3863d353d051d96d2','英语','六年级','陈璐');
# INSERT `choice_course` (`id`,`stu_id`,`course_id`,`time`) VALUES ('672b24ef7a7d4e528d5f83a12e35e12a','22d4d964c84b4f0d8b507494a150b4f2','4358177385ac49b3863d353d051d96d2','2020-09-30 09:55:S');
# INSERT `stu` (`id`,`name`,`school`,`nickname`,`age`,`score`,`phone`,`email`,`address`) VALUES ('a26e0dbce38f4b3caebcdac003c10474','李勇','中国家里蹲小学','liyong',12,'D','13593522449','pingyao@gf.cn','福建省57758');
# INSERT `course` (`id`,`name`,`grade`,`teacher`) VALUES ('6704f94b7eed42ed8e0933002f712fce','化学','三年级','吴英');
# INSERT `choice_course` (`id`,`stu_id`,`course_id`,`time`) VALUES ('63be9245873f40ad9f87ccd3d9270a67','a26e0dbce38f4b3caebcdac003c10474','6704f94b7eed42ed8e0933002f712fce','2020-09-30 09:55:S');
# 执行完成,共生成2组数据


# 参数说明
./test.yml: yml文件必填
-p: 是否打印,指定后打印输出sql语句;默认不打印
-i: 是否插入到数据库,指定后需要--connect参数来指定数据库连接
-o: 文件输出,指定后要输入文件名,sql语句将写入到该文件内
-n: 生成次数,默认为1

项目地址

gitee.com/guojongg/db…