resource模块的测试,对在前面编写的逻辑方法进行单元测试
测试前的配置加载和依赖准备
test/tools/setup.go—测试用例的配置初始化加载
func DevelopmentSetup() {
// 初始化日志实例
zap.DevelopmentSetup()
// 初始化配置, 提前配置好/etc/unit_test.env
err := conf.LoadConfigFromEnv()
if err != nil {
panic(err)
}
// 初始化全局app
if err := app.InitAllApp(); err != nil {
panic(err)
}
}
etc/unit_test.env—在文件中配置测试的数据库连接
MYSQL_HOST =
MYSQL_PORT = 3306
MYSQL_USERNAME = root
MYSQL_PASSWORD =
MYSQL_DATABASE = cmdb
apps/resource/impl/resource_dao.go
构造对用的tablename
type ResourceId struct {
ResourceId string `json:"resource_id"`
}
type Spec struct {
*ResourceId
*resource.Spec
}
func (s *Spec) TableName() string {
return "resource_spec"
}
type Cost struct {
*ResourceId
*resource.Cost
}
func (m *Cost) TableName() string {
return "resource_cost"
}
type Status struct {
*ResourceId
*resource.Status
}
func (m *Status) TableName() string {
return "resource_status"
}
type Tag struct {
*ResourceId
*resource.Tag
}
func (m *Tag) TableName() string {
return "resource_tag"
}
编写具体测试函数
apps/resource/impl/impl_test.go
func init() {
tools.DevelopmentSetup()
impl = app.GetInternalApp(resource.AppName).(resource.Service)
}
var (
impl resource.Service
ctx = context.Background()
)
func TestSaveResource(t *testing.T) {
res := &resource.Resource{
Meta: &resource.Meta{
ResourceId: "instance_01",
Domain: "default",
Namespace: "default",
Env: "测试环境",
SyncAt: time.Now().Unix(),
},
Spec: &resource.Spec{
Vendor: resource.VENDOR_ALIYUN,
ResourceType: resource.TYPE_HOST,
Name: "test01",
},
Cost: &resource.Cost{
RealCost: 80000,
},
Status: &resource.Status{
Phase: "Running",
},
Tags: []*resource.Tag{
{Key: "app", Value: "app_01", Describe: "应用01"},
{Key: "app", Value: "app_02", Describe: "应用02"},
},
}
_, err := impl.SaveResource(ctx, res)
if err != nil {
t.Fatal(err)
}
}
点击测试,结果如下。会显示SQL的具体执行语句
=== RUN TestSaveResource
2023/01/26 12:19:27 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:23
[24.516ms] [rows:1] INSERT INTO `resource_meta` (`resource_id`,`domain`,`namespace`,`env`,`sync_at`,`credential_id`,`create_at`,`serial_number`) VALUES ('instance_02','default','default','测试环境',1674706767,'',0,'')
2023/01/26 12:19:27 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:27
[26.260ms] [rows:1] INSERT INTO `resource_spec` (`resource_id`,`vendor`,`resource_type`,`region`,`zone`,`owner`,`name`,`category`,`type`,`description`,`expire_at`,`update_at`,`release_protection`,`cpu`,`gpu`,`memory`,`storage`,`band_width`) VALUES ('instance_02',0,0,'','','','test01','','','',0,0,NULL,0,0,0,0,0)
2023/01/26 12:19:27 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:31
[24.898ms] [rows:1] INSERT INTO `resource_status` (`resource_id`,`phase`,`lock_mode`,`lock_reason`,`public_ip`,`private_ip`) VALUES ('instance_02','Running','','','','')
2023/01/26 12:19:27 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:35
[23.360ms] [rows:1] INSERT INTO `resource_cost` (`resource_id`,`pay_mode`,`pay_mode_detail`,`sale_price`,`real_cost`,`policy`,`unit_price`) VALUES ('instance_02',NULL,'',0.000000,80000.000000,0.000000,0.000000)
2023/01/26 12:19:27 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:40
[24.273ms] [rows:0] DELETE FROM `resource_tag` WHERE resource_id = 'instance_02'
2023/01/26 12:19:27 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:45
[23.755ms] [rows:1] INSERT INTO `resource_tag` (`resource_id`,`purpose`,`key`,`value`,`describe`,`weight`,`read_only`,`hidden`) VALUES ('instance_02',0,'app','app_01','应用01',0,fals e,false)
2023/01/26 12:19:27 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:45
[15.866ms] [rows:1] INSERT INTO `resource_tag` (`resource_id`,`purpose`,`key`,`value`,`describe`,`weight`,`read_only`,`hidden`) VALUES ('instance_02',0,'app','app_02','应用02',0,fals e,false)
--- PASS: TestSaveResource (0.19s)
PASS
apps/resource/impl/resource.go
添加查询方法
func (s *service) QueryResource(ctx context.Context, req *resource.QueryResourceRequest) (
*resource.ResourceSet, error) {
// 首先查询Meta表
// LEFT JOIN
// 都是用orm, JOIN
// 完全使用SQL
// SELECT
// *
// FROM
// resource_meta m
// LEFT JOIN resource_spec s ON s.resource_id = m.resource_id
// LEFT JOIN resource_cost c ON c.resource_id = m.resource_id
// LEFT JOIN resource_status t ON t.resource_id = m.resource_id
// WHERE m.resource_id='instance_01';
// orm作为SQL builder 来构建复杂SQL
query := s.db.WithContext(ctx).Table("resource_meta m").
Joins("LEFT JOIN resource_spec s ON s.resource_id = m.resource_id").
Joins("LEFT JOIN resource_cost c ON c.resource_id = m.resource_id").
Joins("LEFT JOIN resource_status t ON t.resource_id = m.resource_id")
if req.Domain != "" {
query = query.Where("m.domain = ?", req.Domain)
}
if req.Namespace != "" {
query = query.Where("m.namespace = ?", req.Namespace)
}
if req.Env != "" {
query = query.Where("m.env = ?", req.Env)
}
if req.Status != "" {
query = query.Where("m.env = ?", req.Env)
}
if req.Type != nil {
query = query.Where("s.resource_type = ?", req.Env)
}
if len(req.ResourceIds) > 0 {
query = query.Where("m.resource_id IN (?)", req.ResourceIds)
}
temp := NewResourceSet()
err := query.
Order("m.create_at DESC").
Offset(int(req.Page.ComputeOffset())).
Limit(int(req.Page.PageSize)).
Scan(&temp.Items).
Error
if err != nil {
return nil, err
}
// 数据库里面的数据 和 接口的数据不一定匹配
// 需要在Dao进行转换
set := temp.ResourceSet()
// 统计有多少条
err = query.Count(&set.Total).Error
if err != nil {
return nil, err
}
return set, nil
}
apps/resource/impl/impl_test.go
补充查询resource的测试函数
func TestQueryResource(t *testing.T) {
req := resource.NewQueryResourceRequest()
req.ResourceIds = []string{"instance_04"}
resp, err := impl.QueryResource(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(resp)
}
测试结果
=== RUN TestQueryResource
2023/01/26 12:32:49 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource.go:68
[19.209ms] [rows:1] SELECT * FROM resource_meta m LEFT JOIN resource_spec s ON s.resource_id = m.resource_id LEFT JOIN resource_cost c ON c.resource_id = m.resource_id LEFT JOIN resource_status t ON t.resource_id = m.resource_id WHERE m.resource_id IN ('instance_02') ORDER BY m.create_at DESC LIMIT 20
2023/01/26 12:32:49 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource.go:80
[19.176ms] [rows:1] SELECT count(*) FROM resource_meta m LEFT JOIN resource_spec s ON s.resource_id = m.resource_id LEFT JOIN resource_cost c ON c.resource_id = m.resource_id LEFT JOIN resource_status t ON t.resource_id = m.resource_id WHERE m.resource_id IN ('instance_02') LIMIT 20
impl_test.go:78: total:1 items:{meta:{resource_id:"instance_02" domain:"default" namespace:"default" env:"测试环境" sync_at: 1674706767} spec:{name:"test01"} cost:{real_cost:80000} status:{phase:"Running"}}
--- PASS: TestQueryResource (0.04s)
PASS
apps/resource/impl/resource.go
添加删除资源的方法
func (s *service) DeleteResource(ctx context.Context, res *resource.DeleteResourceRequest) (
*resource.DeleteResourceRequest, error) {
ins := resource.NewResource()
err := s.delete(ctx, res)
if err != nil {
panic(err)
}
ins.Meta.ResourceId = res.ResourceId
return nil, nil
}
apps/resource/impl/resource_dao.go
补充删除对数据库的交互逻辑,使用事务
// 多表联合删除,开启事务
func (s *service) delete(ctx context.Context, res *resource.DeleteResourceRequest) (err error) {
// 开启事务
tx := s.db.WithContext(ctx).Begin()
defer func() {
if err != nil {
tx.Rollback()
return
}
err := tx.Commit().Error
if err != nil {
s.log.Errorf("commit error, %s", err)
}
}()
//DELETE FROM `resource_meta` WHERE resource_id = 'instance_01'
err = tx.Table("resource_meta").Where("resource_id = ?", res.ResourceId).Delete(&resource.Meta{}).Error
if err != nil {
return
}
err = tx.Table("resource_spec").Where("resource_id = ?", res.ResourceId).Delete(&resource.Spec{}).Error
if err != nil {
return
}
err = tx.Table("resource_cost").Where("resource_id = ?", res.ResourceId).Delete(&resource.Status{}).Error
if err != nil {
return
}
err = tx.Table("resource_status").Where("resource_id = ?", res.ResourceId).Delete(&resource.Cost{}).Error
if err != nil {
return
}
err = tx.Table("resource_tag").Where("resource_id = ?", res.ResourceId).Delete(&Tag{}).Error
if err != nil {
return
}
return
}
apps/resource/impl/impl_test.go
删除resource的测试方法
func TestDeleteResource(t *testing.T) {
req := new(resource.DeleteResourceRequest)
req.ResourceId = "instance_03"
req.Namespace = "default"
resp, err := impl.DeleteResource(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(resp)
}
测试结果
=== RUN TestDeleteResource
2023/01/26 12:38:20 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:69
[23.023ms] [rows:1] DELETE FROM `resource_meta` WHERE resource_id = 'instance_03'
2023/01/26 12:38:20 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:73
[22.810ms] [rows:1] DELETE FROM `resource_spec` WHERE resource_id = 'instance_03'
2023/01/26 12:38:20 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:77
[23.311ms] [rows:1] DELETE FROM `resource_cost` WHERE resource_id = 'instance_03'
2023/01/26 12:38:20 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:81
[21.121ms] [rows:1] DELETE FROM `resource_status` WHERE resource_id = 'instance_03'
2023/01/26 12:38:20 F:/go/project/0-shizhanxiangmu/myCMDB/mycmdb/apps/resource/impl/resource_dao.go:86
[22.898ms] [rows:2] DELETE FROM `resource_tag` WHERE resource_id = 'instance_03'
总结
至此,完成了对resource模块数据结构的建立,以及添加,查询和删除资源的逻辑,以及对它们的单元测试。
评论区