快速了解 GO 之依赖注入与 mock测试

更多个人笔记见:
注意点击“继续”,而不是“发现新项目”
github个人笔记仓库 https://github.com/ZHLOVEYY/IT_note
gitee 个人笔记仓库 https://gitee.com/harryhack/it_note
个人学习,学习过程中还会不断补充~ (后续会更新在github上)

例子分析依赖注入

  • 依赖注入(DI)是一种设计模式,通过外部传入依赖对象而非内部创建
  • 解耦是目标,DI是实现手段
传统方式
//传统方式(紧耦合)
func GetUser(id uint) {
    db := gorm.Open(...) // 内部创建依赖
    db.First(...)
}

func GetUser(db *gorm.DB, id uint) { // 依赖从外部注入
    db.First(...)
}
DI方式

真正的依赖注入应该: 依赖抽象而非具体实现,通过接口解耦并便于单元测试

// DI方式(松耦合)
// 1. 定义接口(抽象层)
type Repository interface{...}

// 2. 实现具体存储库
type GormRepository struct{...}

// 3. 业务服务声明依赖接口
type UserService struct {
    repo Repository
}

// 4. 依赖注入点(通常在main/初始化代码)
func main() {
    gormDB := initGorm() // 初始化具体DB连接,这个地方是初始化,自己定义的
    // 将具体实现注入抽象接口!!!!!!
    service := UserService{
        repo: &GormRepository{db: gormDB}
    }
    //接下来 UserService的方法就可以实现直接调用->进一步调用GormRepository(也就是满足Repository接口的结构体的具体方法)
}

具体例子分析:

// 定义数据模型
type User struct {
	ID       uint
	Name     string
	Email    string
	Password string
}

// 1. 定义接口(抽象层)
type Repository interface {
	FindUserByID(id uint) (*User, error)
	CreateUser(user *User) error
	UpdateUser(user *User) error
	DeleteUser(id uint) error
}
// 2. 实现具体存储库
type GormRepository struct {
	db *gorm.DB
}

// 实现Repository接口的方法
func (r *GormRepository) FindUserByID(id uint) (*User, error) {
	var user User
	result := r.db.First(&user, id)
	if result.Error != nil {
		return nil, result.Error
	}
	return &user, nil
}

func (r *GormRepository) CreateUser(user *User) error {
	return r.db.Create(user).Error
}

func (r *GormRepository) UpdateUser(user *User) error {
	return r.db.Save(user).Error
}

func (r *GormRepository) DeleteUser(id uint) error {
	return r.db.Delete(&User{}, id).Error
}
// 3. 业务服务声明依赖接口
type UserService struct {
	repo Repository
}

// 业务方法使用注入的Repository  这里不是需要满足Repository,都是实际业务方法
//repo 只是向下进一步调用
func (s *UserService) GetUser(id uint) (*User, error) {
	return s.repo.FindUserByID(id) //调用对应的FindUserByID方法
}

func (s *UserService) RegisterUser(name, email, password string) (*User, error) {
	user := &User{
		Name:     name,
		Email:    email,
		Password: password, // 实际应用中应该加密
	}
	
	err := s.repo.CreateUser(user)
	if err != nil {
		return nil, fmt.Errorf("failed to register user: %w", err)
	}
	
	return user, nil
}

func (s *UserService) UpdateUserProfile(id uint, name string) error {
	user, err := s.repo.FindUserByID(id)
	if err != nil {
		return fmt.Errorf("user not found: %w", err)
	}
	
	user.Name = name
	return s.repo.UpdateUser(user)
}

// 4. 依赖注入点(通常在main/初始化代码)
func main() {
	// 初始化数据库连接,这里是可以修改的
	gormDB := initGorm()
	
	// 将具体实现注入抽象接口,而不是内部使用具体的
	userService := &UserService{
		repo: &GormRepository{db: gormDB},
	}
	
	// 使用服务
	user, err := userService.GetUser(1)
	if err != nil {
		fmt.Printf("Error getting user: %v\n", err)
		return
	}
	fmt.Printf("Found user: %s (%s)\n", user.Name, user.Email)
	
	// 注册新用户
	newUser, err := userService.RegisterUser("John Doe", "john@example.com", "password123")
	if err != nil {
		fmt.Printf("Error registering user: %v\n", err)
		return
	}
	fmt.Printf("Registered new user with ID: %d\n", newUser.ID)
}

// 初始化GORM数据库连接,对应 main 一开始
func initGorm() *gorm.DB {
	db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}
	// 自动迁移
	db.AutoMigrate(&User{})
	return db
}

构造函数注入依赖

在 main 中注入的时候可以修改:

// 使用构造函数注入依赖
func NewUserService(repo Repository) *UserService {
	return &UserService{
		repo: repo,
	}
}
// 使用示例
func main() {
	gormDB := initGorm()
	repo := &GormRepository{db: gormDB}
	// 通过构造函数注入依赖
	userService := NewUserService(repo)
	// 使用服务...
}

在每一层都可以使用函数方式的注入,所以代码中能看到很多“New”

容器式注入
// 简单的DI容器
type Container struct {
	services map[string]interface{}
}

func NewContainer() *Container {
	return &Container{
		services: make(map[string]interface{}),
	}
}

func (c *Container) Register(name string, service interface{}) {
	c.services[name] = service
}

func (c *Container) Get(name string) interface{} {
	return c.services[name]
}

// 使用容器
func main() {
	container := NewContainer()
	// 注册服务
	gormDB := initGorm()
	container.Register("repository", &GormRepository{db: gormDB})
	container.Register("userService", NewUserService(container.Get("repository").(Repository)))
	// 获取服务
	userService := container.Get("userService").(*UserService)
	// 使用服务...
}

(补充)Mock测试

// 用于测试的Mock存储库
type MockRepository struct {
	users map[uint]*User
}

//构造函数但是没有注入依赖,因为只是初始化了一个内部状态(`users` 映射),但没有接收任何外部依赖
func NewMockRepository() *MockRepository {
	return &MockRepository{
		users: make(map[uint]*User),  //**​初始时确实没有存储任何用户数据**
	}
}

func (r *MockRepository) FindUserByID(id uint) (*User, error) {
	user, exists := r.users[id]
	if !exists {
		return nil, fmt.Errorf("user not found")
	}
	return user, nil
}

func (r *MockRepository) CreateUser(user *User) error {
	if user.ID == 0 {
		user.ID = uint(len(r.users) + 1)
	}
	r.users[user.ID] = user
	return nil
}

func (r *MockRepository) UpdateUser(user *User) error {
	if _, exists := r.users[user.ID]; !exists {
		return fmt.Errorf("user not found")
	}
	r.users[user.ID] = user
	return nil
}

func (r *MockRepository) DeleteUser(id uint) error {
	if _, exists := r.users[id]; !exists {
		return fmt.Errorf("user not found")
	}
	delete(r.users, id)
	return nil
}

// 测试示例,这里用到了 GO 中的测试!
func TestUserService() {
	// 使用Mock存储库进行测试
	mockRepo := NewMockRepository()  //利用函数初始化
	userService := &UserService{repo: mockRepo}  //mockRepo是满足对应的 Repository 接口的方法的
	
	//接下来可以使用UserService 的结构体方法了
	// 测试注册用户
	user, _ := userService.RegisterUser("Test User", "test@example.com", "password")
	// 测试获取用户
	foundUser, _ := userService.GetUser(user.ID) //GetUser中会调用mockRepo的方法的
	fmt.Printf("Found user in test: %s\n", foundUser.Name)
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值