搞定Go單元測試(四)—— 依賴注入框架(wire)

作者:水立方 鏈接:https://juejin.cn/post/6844903853536575501 來源:稀土掘金 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

在第一篇文章中提到過,為了讓代碼可測,需要用依賴注入的方式來構建我們的對象,而通常我們會在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 依賴配置 和 PersonRepository
    personService := NewPersonService(config, personRepository)
    // NewServer 依賴配置和PersonService
    server := NewServer(config, personService)
    server.Run()
}

實踐表明,修改有大量依賴關係的初始化代碼是一項乏味且耗時的工作。這個時候,我們就需要依賴注入框架來幫忙,簡化初始化代碼。

https://blog.drewolson.org/dependency-injection-in-go

使用依賴注入框架——wire

What is wire?

wire是google開源的依賴注入框架。或者引用官方的話來說: "Wire is a code generation tool that automates connecting components using dependency injection"

https://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: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的呢?我們需要寫一個函數來告訴它:

  • 定義injector的函數簽名
  • 在函數中使用wire.Build方法列舉生成injector所需的provider

例如:

// 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函數。

例子

https://github.com/DrmagicE/wire-examples

注意

截止本文發布前,官方表明wire的項目狀態是alpha,還不適合到生產環境,API存在變化的可能。 雖然是alpha,但其主要作用是為我們生成依賴注入代碼,其生成的代碼十分通俗易懂,在做好版本控制的前提下,即使是API發生變化,也不會對生成環境造成多壞的影響。我認為還是可以放心使用的。

總結一下

本篇是本系列的最後一篇,回顧前幾篇文章,我們以單元測試的原理與基本思想為基礎,介紹了表格驅動測試方法,gomock,testify,wire這幾樣實用工具,經歷了"能寫單元測試"到"寫好單元測試"不斷優化的過程。希望本系列文章能讓你有所收穫。

Reference

© Kimi Tsai all right reserved.            Updated : 2023-07-12 09:04:53

results matching ""

    No results matching ""

    results matching ""

      No results matching ""