Get Go Unit Testing (4) — Платформа внедрения зависимостей (проводная версия)

Go

Как упоминалось в первой статье, чтобы сделать код пригодным для тестирования, нам нужно использовать внедрение зависимостей для создания наших объектов, и обычно мыmain.goсделать внедрение зависимостей, что приводит кmain.goбудет становиться все более и более раздутым. Чтобы модульное тестирование проходило гладко,main.goЗа счет своего якобы стройного и стройного тела. слишком толстыйmain.goПлохой сигнал, в этой статье будет представлена ​​структура внедрения зависимостей (wire), предназначенная для помощиmain.goВернуться в форму.

раздутый основной

существует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()
}

Практика показала, что изменение кода инициализации с большим количеством зависимостей — утомительное и трудоемкое занятие. В настоящее время нам нужна структура внедрения зависимостей, чтобы помочь и упростить код инициализации.

Приведенный выше код взят из:blog.Drew Olson.org/dependency-…

Использовать инфраструктуру внедрения зависимостей — провод

What is wire?

Wire — это платформа внедрения зависимостей с открытым исходным кодом от Google. Или, цитируя официальные слова: «Wire — это инструмент генерации кода, которыйautomates connecting components using dependency injection".

github.com/google/wire

Why wire?

В дополнение к проводу, инфраструктура внедрения зависимостей Go также имеет Uber.digи Facebookinject, все они используют механизм отражения для реализации внедрения зависимостей во время выполнения (внедрение зависимостей во время выполнения), а wire использует генерацию кода для внедрения внедрения зависимостей во время компиляции (внедрение зависимостей во время компиляции). Во-вторых, снижение производительности при использовании отражения, и, что более важно, отражение затрудняет отслеживание и отладку кода (отражение сделает недействительным сочетание клавиш Ctrl+left...). Код, сгенерированный проводом, соответствует повседневным привычкам программистов, и его очень легко понять и отладить.
Что касается преимуществ проволоки, более подробное описание есть в официальном блоге:blog.golang.org/wire

How does it work?

Эта часть контента относится к официальному сообщению в блоге:blog.golang.org/wire

wire имеет два основных понятия: провайдер и инжектор.

provider

Провайдер — это обычная функция Go, которую можно рассматривать как конструктор объекта, через провайдер мы сообщаем проводу зависимость объекта:

// 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

Инжектор — это функция, генерируемая wire.Мы получаем нужный нам объект или значение, вызывая инжектор.Инжектор будет вызывать функцию провайдера по порядку согласно зависимостям:

// 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
}

Инжектор помогает нам делать шаги инициализации зависимостей по порядку, мы вmain.goнужно только позвонитьinitUserStoreметод, чтобы получить объект, который мы хотим.

Так откуда wire знает, как генерировать инжекторы? Нам нужно написать функцию, чтобы сказать это:

  • Определите сигнатуру функции инжектора
  • использовать по назначениюwire.BuildМетод перечисляет поставщиков, необходимых для создания инжектора.

Например:

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

С помощью вышеуказанной функции провод может знать, как создать инжектор. Шаги инжектора генерации проволоки описаны следующим образом:

  1. Определите сигнатуру сгенерированной функции инжектора:func initUserStore(info ConnectionInfo) (*UserStore, error)
  2. Первый параметр возвращаемого значения восприятия:*UserStore
  3. экзаменwire.Buildсписок, найти*UserStoreпровайдер:NewUserStore
  4. подписан функциейfunc NewUserStore(cfg *Config, db *mysql.DB)учитьсяNewUserStoreзависит от*Config, и*mysql.DB
  5. экзаменwire.Buildсписок, найти*Configи*mysql.DBпровайдер:NewDefaultConfigиNewDB
  6. подписан функциейfunc NewDefaultConfig() *Configучиться*ConfigДругих зависимостей нет.
  7. подписан функциейfunc NewDB(info *ConnectionInfo) (*mysql.DB, error)учиться*mysql.DBзависит отConnectionInfo.
  8. экзаменwire.Buildсписок, не нашелConnectionInfoпоставщика, но соответствующий тип входного параметра находится в сигнатуре функции инжектора, и этот параметр используется непосредственно какNewDBввод.
  9. Возвращаемое значение восприятия Второй параметрerror
  10. ....
  11. По зависимостям последовательно вызываются функции-провайдеры и собираются функции-инжекторы.

взять каштан

Каштановый портал:wire-examples

Уведомление

На момент публикации этой статьи официальные лица указали, что статус проекта wire — альфа, что еще не подходит для производственных сред, и API может измениться.
Несмотря на то, что это альфа-версия, ее основная функция заключается в создании для нас кода внедрения зависимостей.Сгенерированный код очень прост для понимания.При условии хорошего контроля версий, даже если API изменится, это не нанесет большого ущерба среде генерации. , Влияние. Я думаю, что это все еще безопасно для использования.

в заключении

Эта статья является последней в этой серии.Оглядываясь назад на предыдущие статьи, основываясь на принципах и основных идеях модульного тестирования, мы представили метод тестирования на основе таблиц, gomock, testify, wire и другие практические инструменты. непрерывная оптимизация от «написания модульных тестов» до «написания хороших модульных тестов». Я надеюсь, что эта серия статей может быть полезной для вас.