搞定Go单元测试(四)—— 依赖注入框架(wire)

栏目: Go · 发布时间: 6年前

内容简介:在第一篇文章中提到过,为了让代码可测,需要用依赖注入的方式来构建我们的对象,而通常我们会在在对于小型项目而言,依赖的数量比较少,初始化代码不会很多,不需要引入依赖注入框架。但对于依赖较多的中大型项目,初始化代码又臭又长,可读性和维护性变的很差,随意感受一下:

在第一篇文章中提到过,为了让代码可测,需要用依赖注入的方式来构建我们的对象,而通常我们会在 main.go 做依赖注入,这就导致 main.go 会越来越臃肿。为了让单元测试得以顺利进行, main.go 牺牲了它本应该纤细苗条的身材。太胖的 main.go 可不是什么好的信号,本篇将介绍依赖注入框架(wire),致力于帮助 main.go 恢复身材。

臃肿的main

main.go 中做依赖注入,意味着在初始化代码中我们要管理:

  1. 依赖的初始化顺序
  2. 依赖之间的关系

对于小型项目而言,依赖的数量比较少,初始化代码不会很多,不需要引入依赖注入框架。但对于依赖较多的中大型项目,初始化代码又臭又长,可读性和维护性变的很差,随意感受一下:

func main() {
    config := NewConfig()
    // db依赖配置
    db, err := ConnectDatabase(config) 
    if err != nil {
        panic(err)
    }
    // PersonRepository 依赖db
    personRepository := NewPersonRepository(db) 
    // PersonService 依赖db 和 PersonRepository
    personService := NewPersonService(config, personRepository)
    // NewServer 依赖配置和PersonService
    server := NewServer(config, personService)
    server.Run()
}
复制代码

实践表明,修改有大量依赖关系的初始化代码是一项乏味且耗时的工作。这个时候,我们就需要依赖注入框架来帮忙,简化初始化代码。

上述代码来自: blog.drewolson.org/dependency-…

使用依赖注入框架——wire

What is wire?

wire是google开源的依赖注入框架。或者引用官方的话来说:“Wire is a code generation tool that automates connecting components using dependency injection ”。

github.com/google/wire

Why wire?

除了wire,Go的依赖注入框架还有Uber的 dig 和Facebook的 inject ,它们都是使用反射机制来实现运行时依赖注入(runtime dependency injection),而wire则是采用代码生成的方式来达到编译时依赖注入(compile-time dependency injection)。使用反射带来的性能损失倒是其次,更重要的是反射使得代码难以追踪和调试(反射会令Ctrl+左键失效...)。而wire生成的代码是符合 程序员 常规使用习惯的代码,十分容易理解和调试。

关于wire的优点,在官方博文上有更详细的的介绍:blog.golang.org/wire

How does it work?

本部分内容参考官方博文:blog.golang.org/wire

wire有两个基本的概念:provider和injector。

provider

provider就是普通的 Go 函数,可以把它看作是某对象的构造函数,我们通过provider告诉wire该对象的依赖情况:

// NewUserStore是*UserStore的provider,表明*UserStore依赖于*Config和 *mysql.DB.
func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}

// NewDefaultConfig是*Config的provider,没有依赖
func NewDefaultConfig() *Config {...}

// NewDB是*mysql.DB的provider,依赖于ConnectionInfo
func NewDB(info ConnectionInfo) (*mysql.DB, error) {...}

// UserStoreSet 可选项,可以使用wire.NewSet将通常会一起使用的依赖组合起来。
var UserStoreSet = wire.NewSet(NewUserStore, NewDefaultConfig)
复制代码

injector

injector是wire生成的函数,我们通过调用injector来获取我们所需的对象或值,injector会按照依赖关系,按顺序调用provider函数:

// File: wire_gen.go
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject

// initUserStore是由wire生成的injector
func initUserStore(info ConnectionInfo) (*UserStore, error) {
    // *Config的provider函数
    defaultConfig := NewDefaultConfig()
    // *mysql.DB的provider函数
    db, err := NewDB(info)
    if err != nil {
        return nil, err
    }
    // *UserStore的provider函数
    userStore, err := NewUserStore(defaultConfig, db)
    if err != nil {
        return nil, err
    }
    return userStore, nil
}
复制代码

injector帮我们把按顺序初始化依赖的步骤给做了,我们在 main.go 中只需要调用 initUserStore 方法就能得到我们想要的对象了。

那么wire是怎么知道如何生成injector的呢?我们需要写一个函数来告诉它:

wire.Build

例如:

// initUserStore用于声明injector的函数签名
func initUserStore(info ConnectionInfo) (*UserStore, error) {  
    // wire.Build声明要获取一个UserStore需要调用到哪些provider函数
    wire.Build(UserStoreSet, NewDB)
    return nil, nil  // 这些返回值wire并不关心。
}
复制代码

有了上面的函数,wire就可以得知如何生成injector了。wire生成injector的步骤描述如下:

  1. 确定所生成injector函数的函数签名: func initUserStore(info ConnectionInfo) (*UserStore, error)
  2. 感知返回值第一个参数是 *UserStore
  3. 检查 wire.Build 列表,找到 *UserStore 的provider: NewUserStore
  4. 由函数签名 func NewUserStore(cfg *Config, db *mysql.DB) 得知 NewUserStore 依赖于 *Config , 和 *mysql.DB
  5. 检查 wire.Build 列表,找到 *Config*mysql.DB 的provider: NewDefaultConfigNewDB
  6. 由函数签名 func NewDefaultConfig() *Config 得知 *Config 没有其他依赖了。
  7. 由函数签名 func NewDB(info *ConnectionInfo) (*mysql.DB, error) 得知 *mysql.DB 依赖于 ConnectionInfo
  8. 检查 wire.Build 列表,找不到 ConnectionInfo 的provider,但在injector函数签名中发现匹配的入参类型,直接使用该参数作为 NewDB 的入参。
  9. 感知返回值第二个参数是 error
  10. ....
  11. 按依赖关系,按顺序调用provider函数,拼装injector函数。

举个栗子

栗子传送门: wire-examples

注意

截止本文发布前,官方表明wire的项目状态是alpha,还不适合到生产环境,API存在变化的可能。

虽然是alpha,但其主要作用是为我们生成依赖注入代码,其生成的代码十分通俗易懂,在做好版本控制的前提下,即使是API发生变化,也不会对生成环境造成多坏的影响。我认为还是可以放心使用的。

总结一下

本篇是本系列的最后一篇,回顾前几篇文章,我们以单元测试的原理与基本思想为基础,介绍了表格驱动测试方法,gomock,testify,wire这几样实用工具,经历了“能写单元测试”到“写好单元测试”不断优化的过程。希望本系列文章能让你有所收获。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Definitive Guide to MongoDB

The Definitive Guide to MongoDB

Peter Membrey、Wouter Thielen / Apress / 2010-08-26 / USD 44.99

MongoDB, a cross-platform NoSQL database, is the fastest-growing new database in the world. MongoDB provides a rich document orientated structure with dynamic queries that you’ll recognize from RDMBS ......一起来看看 《The Definitive Guide to MongoDB》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换