You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

397 lines
16 KiB

package test
import (
"fmt"
"goskeleton/app/global/variable"
"goskeleton/app/utils/gorm_v2"
_ "goskeleton/bootstrap"
"sync"
"testing"
"time"
)
// gorm v2 操作数据库单元测试
// 单元测试为了不影响自带的数据库,我们将单元测试涉及到的表创建在了 独立的额数据库: db_ginskeleton2_test
// 测试本篇首先保证 config/gorm_v2.yml 文件配置正确,相关配置项 IsInitGolobalGormMysql = 1 并且是设置连接的数据为db_ginskeleton2_test
// 单元测试涉及到的数据库下载地址https://wwt.lanzoul.com/ivT3A06cq35i
// 本文件测试到的相关数据表由于数据量较大, 最终的数据库文件没有放置在本项目骨架中,如果你动手能力很强,可以通过 issue 留言获取,重新进行测试
// 更多使用用法参见官方文档https://gorm.io/zh_CN/docs/v2_release_note.html
// 模拟创建 3 个数据表,请在数据库按照结构体字段自行创建,字段全部使用小写
type tb_users struct {
Id uint `json:"id" gorm:"primaryKey" `
UserName string `json:"user_name"`
Age uint8 `json:"age"`
Addr string `json:"addr"`
Email string `json:"email"`
Phone string `json:"phone"`
Remark string `json:"remark"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
func (*tb_users) TableName() string {
return "tb_users"
}
//角色表
type tb_role struct {
Id uint `json:"id" gorm:"primaryKey" `
Name string `json:"name"`
DisplayName string `json:"display_name"`
Description string `json:"description"`
Remark string `json:"remark"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
func (*tb_role) TableName() string {
return "tb_role"
}
// 用户登录日志
type tb_user_log struct {
Id int `gorm:"primaryKey" `
UserId int
Ip string
LoginTime string
Remark string
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// 如果不自定义默认使用的是表名的复数形式Tb_user_logs
func (*tb_user_log) TableName() string {
return "tb_user_log"
}
// code_list表
type tb_code_list struct {
Code string
Name string
CompanyName string
Concepts string
ConceptsDetail string
Province string
City string
Status uint8
Remark string
CreatedAt string
UpdatedAt string
}
// 如果不自定义默认使用的是表名的复数形式Tb_user_logs
func (*tb_code_list) TableName() string {
return "tb_code_list"
}
// 查询
func TestGormSelect(t *testing.T) {
// 查询 tb_users由于没有配置指定的主从数据库。所以默认连接的是
var users []tb_users
var roles []tb_role
// tb_users 查询数据会从 db_test 查询, 整个语句没有指定表名,那么就会从 Find 函数参数 &users 上绑定的 tableName函数的返回值中获取表名
result := variable.GormDbMysql.Select("id", "user_name", "phone", "email", "remark").Where("user_name like ?", "%test%").Find(&users)
if result.Error != nil {
t.Errorf("单元测试失败,错误明细:%s\n", result.Error.Error())
}
fmt.Printf("tb_users表数据%v\n", users)
result = variable.GormDbMysql.Where("name like ?", "%test%").Find(&roles)
if result.Error != nil {
t.Errorf("单元测试失败,错误明细:%s\n", result.Error.Error())
}
fmt.Printf("tb_roles表数据%v\n", roles)
}
// 新增
func TestGormInsert(t *testing.T) {
var usrLog = &tb_user_log{
UserId: 3,
Ip: "192.168.1.110",
LoginTime: time.Now().Format("2006-01-02 15:04:05"),
Remark: "备注信息1028",
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
UpdatedAt: time.Now().Format("2006-01-02 15:04:05"),
}
// 方式1相关sql 语句: insert into Tb_user_log(user_id,ip,login_time,CreatedAt,updated_at) values(1,"192.168.1.10","当前时间","当前时间")
result := variable.GormDbMysql.Create(usrLog)
if result.RowsAffected < 0 {
t.Error("新增失败,错误详情:", result.Error.Error())
}
// 方式2相关sql 语句: insert into Tb_user_log(user_id,ip,remark) values(1,"192.168.1.10","备注信息001")
result = variable.GormDbMysql.Select("user_id", "ip", "remark").Create(usrLog)
if result.RowsAffected < 0 {
t.Error("新增失败,错误详情:", result.Error.Error())
}
}
// 修改
func TestGormUpdate(t *testing.T) {
var usrLog = tb_user_log{
Id: 13, // 更新操作一定要指定主键Id
UserId: 3,
Ip: "127.0.0.1",
LoginTime: "2008-08-08 08:08:08",
Remark: "整个结构体对应的字段全部更新",
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
UpdatedAt: time.Now().Format("2006-01-02 15:04:05"),
}
// 整个结构体全量更新
result := variable.GormDbMysql.Save(&usrLog)
if result.RowsAffected < 0 {
t.Error("update失败错误详情", result.Error.Error())
}
// 定义更新字段的map 键值对
var relaValue = map[string]interface{}{
"user_id": 66,
"ip": "192.168.6.66",
"login_time": time.Now().Format("2006-01-02 15:04:05"),
"remark": "指定字段更新,备注信息",
}
// 指定字段更新更新sql update Tb_user_log set user_id=66ip='192.168.6.66' , login_time='当前时间', remark='指定字段更新,备注信息' where id=11
result = variable.GormDbMysql.Model(&usrLog).Select("user_id", "ip", "login_time", "remark").Where("id=?", 13).Updates(relaValue)
if result.RowsAffected < 0 {
t.Error("update失败错误详情", result.Error.Error())
}
}
// 删除
func TestGormDelete(t *testing.T) {
// 定义一个只带有ID 的相关表结构体
var key_primary_struct = tb_role{
Id: 4,
}
// 方法1 sqldelete from tb_role where id =4
result := variable.GormDbMysql.Delete(key_primary_struct)
if result.RowsAffected < 0 {
t.Error("delete失败错误详情", result.Error.Error())
}
// 方法2 sqldelete from tb_role where id =5
result = variable.GormDbMysql.Delete(&tb_role{}, 5)
if result.RowsAffected < 0 {
t.Error("delete失败错误详情", result.Error.Error())
}
}
// 原生sql
func TestRawSql(t *testing.T) {
// 查询类
var receive []tb_user_log
variable.GormDbMysql.Raw("select * from tb_user_log where id>?", 0).Scan(&receive)
fmt.Printf("%v\n", receive)
//var dest=make([]string,0)
//_=sql_res_to_tree.CreateSqlResFormatFactory().ScanToTreeData(receive,&dest)
//执行类
result := variable.GormDbMysql.Exec("update tb_user_log set remark=? where id=?", "gorm原生sql执行修改操作", 17)
if result.RowsAffected < 0 {
t.Error("原生sql执行失败错误详情", result.Error.Error())
}
}
func TestBatchInsertSql(t *testing.T) {
// 查询类
sql := `
INSERT INTO tb_auth_post_mount_has_menu_button(fr_auth_post_mount_has_menu_id,fr_auth_button_cn_en_id)
SELECT 91,4 FROM DUAL WHERE NOT EXISTS(SELECT 1 FROM tb_auth_post_mount_has_menu_button a WHERE a.fr_auth_post_mount_has_menu_id=91 AND a.fr_auth_button_cn_en_id=4)
`
for i := 0; i < 100; i++ {
result := variable.GormDbMysql.Exec(sql)
if result.Error != nil {
t.Error("原生sql执行失败错误详情", result.Error.Error())
}
}
}
// 性能测试(大量查询计算耗时,评测性能)
func TestUseTime(t *testing.T) {
//循环查询100次每次查询3500条数据累计查询数据量为 35 万, 计算总耗时
var receives []tb_code_list
var time1 = time.Now()
for i := 0; i < 100; i++ {
receives = make([]tb_code_list, 0)
variable.GormDbMysql.Model(tb_code_list{}).Select("code", "name", "company_name", "concepts_detail", "province", "city", "remark", "status", "created_at", "updated_at").Where("id<=?", 3500).Find(&receives)
}
fmt.Printf("gorm数据遍历完毕最后一次条数%d\n", len(receives))
//经过测试遍历处理35万条数据需要 1034 毫秒,不同配置的电脑耗时不一样
fmt.Printf("本次耗时(毫秒):%d\n", time.Now().Sub(time1).Milliseconds())
// 直接使用 gorm 的原生
//for i:=0;i<100;i++{
// receives=make([]tb_code_list,0)
// variable.GormDbMysql.Raw("SELECT `code`, `name`, `company_name`, `concepts`, `concepts_detail`, `province`, `city`, `remark`, `status`, `CreatedAt`, `updated_at` FROM `tb_code_list` where id<3500 ").Find(&receives)
//}
//fmt.Printf("gorm 原生sql数据遍历完毕最后一次条数%d\n",len(receives))
//// 经过测试遍历处理35万条数据需要 4.58 秒
//fmt.Printf("本次耗时(毫秒):%d\n",time.Now().Sub(time1).Milliseconds())
}
// 性能测试(并发与连接池)
func TestCocurrent(t *testing.T) {
// SELECT `code`, `name`, `company_name`, `concepts`, `concepts_detail`, `province`, `city`, `remark`, `status`, `created_at`, `updated_at` FROM `tb_code_list` where id<3500;
var wg sync.WaitGroup
// 数据库的并发最大连接数建议设置为 128, 后续测试将通过测试数据验证
var conNum = make(chan uint16, 128)
wg.Add(1000)
time1 := time.Now()
for i := 1; i <= 1000; i++ {
conNum <- 1
go func() {
defer func() {
<-conNum
wg.Done()
}()
var received []tb_code_list
variable.GormDbMysql.Table("tb_code_list").Select("code", "name", "company_name", "province", "city", "remark", "status", "created_at", "updated_at").Where("id<=?", 3500).Find(&received)
//fmt.Printf("本次读取的数据条数:%d\n",len(received))
}()
}
wg.Wait()
fmt.Printf("耗时ms:%d\n", time.Now().Sub(time1).Milliseconds())
// 测试结果2022-06-13 补充,以下测试数据为 I7-4代机器在I7-12代机器上面耗时非常少128并发只需要 3.13 秒就完成了本单元测试
// 1.数据库并发在 1000 相当于有1000个客户端连接操作数据库可以在数据库使用 show processlist 自行实时刷新观察、验证),
// 2.并发设置为 1000累计查询、返回结果的数据条数350万. 最终耗时:(14.28s)
// 3.并发设置为 500累计查询、返回结果的数据条数350万. 最终耗时:(14.03s)
// 4.并发设置为 250累计查询、返回结果的数据条数350万. 最终耗时:(13.57s)
// 5.并发设置为 128累计查询、返回结果的数据条数350万. 最终耗时:(13.27s) // 由此可见数据库并发性能最优值就是同时有128个连接,该值相当于抛物线的最高性能点
// 6.并发设置为 100累计查询、返回结果的数据条数350万. 最终耗时:(13.43s)
// 7.并发设置为 64累计查询、返回结果的数据条数350万. 最终耗时:(15.10s)
}
// 面对复杂场景,需要多个客户端连接到部署在多个不同服务器的 mysql、sqlserver、postgresql 等数据库时,
// 由于配置文件config/gorm_v2.yml只提供了一份mysql连接无法满足需求这时您可以通过自定义参数直接连接任意数据库获取一个数据库句柄供业务使用
func TestCustomeParamsConnMysql(t *testing.T) {
// 定义一个查询结果接受结构体
type DataList struct {
Id int
Username string
Last_login_ip string
Status int
}
// 设置动态参数连接任意多个数据库以mysql为例进行单元测试
// 参数结构体 Write 和 Read 只有设置了具体指才会生效否则程序自动使用配置目录config/gorm_v.yml中的参数
confPrams := gorm_v2.ConfigParams{
Write: struct {
Host string
DataBase string
Port int
Prefix string
User string
Pass string
Charset string
}{Host: "127.0.0.1", DataBase: "db_test", Port: 3306, Prefix: "tb_", User: "root", Pass: "DRsXT5ZJ6Oi55LPQ", Charset: "utf8"},
Read: struct {
Host string
DataBase string
Port int
Prefix string
User string
Pass string
Charset string
}{Host: "127.0.0.1", DataBase: "db_stocks", Port: 3306, Prefix: "tb_", User: "root", Pass: "DRsXT5ZJ6Oi55LPQ", Charset: "utf8"}}
var vDataList []DataList
//gorm_v2.GetSqlDriver 参数介绍
// sqlType mysql 、sqlserver、postgresql 等数据库库类型
// readDbIsOpen 是否开启读写分离1表示开启读数据库的配置那么 confPrams.Read 参数部分才会生效; 0 则表示 confPrams.Read 部分参数直接忽略(即 读、写同库)
// confPrams 动态配置的数据库参数
// 此外,其他参数,例如数据库连接池等,则直接调用配置项数据库连接池参数,动态不需要配置,这部分对实际操作影响不大
if gormDbMysql, err := gorm_v2.GetSqlDriver("mysql", 0, confPrams); err == nil {
gormDbMysql.Raw("select id,username,status,last_login_ip from tb_users").Find(&vDataList)
fmt.Printf("Read 数据库查询结果:%v\n", vDataList)
res := gormDbMysql.Exec("update tb_users set real_name='Write数据库更新' where id<=2 ")
fmt.Printf("Write 数据库更新以后的影响行数:%d\n", res.RowsAffected)
}
}
//将结果集数据扫描到树形结构体
// 定义一个树形结构体将原始sql集数据树形化
// 将sql结果集扫描为树形结构数据
// 关于sql结果树形化更多的用法参考独立的文档即可https://gitee.com/daitougege/sql_res_to_tree
//func TestSqlResultToTreeStruct(t *testing.T) {
// // 定义一个原始数据集的接受结构体
// var res1 []struct {
// Id int
// Name string
// Fid int
// }
// sql := `
// SELECT id,fid,name FROM tb_province_city WHERE id IN(1,25,321) OR path_info LIKE '0,1,25,321,2721%'
// `
//
// variable.GormDbMysql.Raw(sql).Find(&res1)
// fmt.Printf("%+v\n", res1)
//
// type ProvinceCity struct {
// Id int `primaryKey:"yes" json:"id"`
// Name string `json:"name"`
// Fid int `fid:"Id" json:"fid"`
// Children []ProvinceCity `json:"children"`
// }
// var dest = make([]ProvinceCity, 0)
// if err := sql_res_to_tree.CreateSqlResFormatFactory().ScanToTreeData(res1, &dest); err == nil {
//
// fmt.Printf("%v\n", dest)
// bytes, _ := json.Marshal(dest)
// fmt.Printf("\n%s\n", bytes)
// } else {
// t.Errorf("%s\n", err)
// }
//
// // 通过反射解剖结构体的字段以及父子关系
//
//}
// sqlserver 数据库测试, 以查询为例其他操作参见mysql
// 请在配置项 config > gorm_v2.yml 中sqlserver 部分,正确配置数据库参数
// 设置 IsInitGolobalGormSqlserver =1 ,程序自动初始化全局变量
func TestSqlserver(t *testing.T) {
var users []tb_users
// 执行类sql如果配置了读写分离该命令会在 write 数据库执行
result := variable.GormDbSqlserver.Exec("update tb_users set remark='update 操作 write数据库' where id=?", 1)
if result.Error != nil {
t.Errorf("单元测试失败,错误明细:%s\n", result.Error.Error())
}
// 查询类,如果配置了读写分离,该命令会在 read 数据库执行
result = variable.GormDbSqlserver.Table("tb_users").Select("id", "user_name", "pass", "remark").Where("id > ?", 0).Find(&users)
if result.Error != nil {
t.Errorf("单元测试失败,错误明细:%s\n", result.Error.Error())
}
fmt.Printf("sqlserver数据查询结果%v\n", users)
}
// PostgreSql 数据库测试
// 请在配置项 config > gorm_v2.yml 中PostgreSql 部分,正确配置数据库参数。
// 设置 IsInitGolobalGormPostgreSql =1 ,程序自动初始化全局变量
func TestPostgreSql(t *testing.T) {
var users []tb_users
// 执行类sql如果配置了读写分离该命令会在 write 数据库执行
result := variable.GormDbPostgreSql.Exec("update web.tb_users set remark='update 操作 write数据库' where id=?", 1)
if result.Error != nil {
t.Errorf("单元测试失败,错误明细:%s\n", result.Error.Error())
}
// 查询类,如果配置了读写分离,该命令会在 read 数据库执行
result = variable.GormDbPostgreSql.Table("web.tb_users").Select("").Select("id", "user_name", "age", "addr", "remark").Where("id > ?", 0).Find(&users)
if result.Error != nil {
t.Errorf("单元测试失败,错误明细:%s\n", result.Error.Error())
}
fmt.Printf("sqlserver数据查询结果%v\n", users)
}