【问题标题】:Dependency Injection & Testing依赖注入和测试
【发布时间】:2018-01-27 14:13:34
【问题描述】:

我正在开发一个小型 Go 应用程序,它基本上是各种密码存储(Ansible Vault、Hashicorp Vault、Chef Vault 等)的包装器。这个想法是:在我的各种配置脚本中,我可以使用我的 Go 包装器来获取秘密,如果我们决定在幕后切换密码存储,则不需要在我的项目中更新所有接口。

我正在尝试为此应用程序设置适当的测试,并在此过程中试图找出注入我的依赖项的最佳方法。

例如,假设项目名为secrets。我的实现之一是ansible。而ansible实现需要自己的parser,需要打开自己的connection到ansible vault,才能取回数据。

所以我可能有以下几点:

package secrets

type PasswordStore interface {
    GetKey(key string) (string, error)
}

func New(backend string, config map[string]interface{}) (PasswordStore, error) {
    switch backend {
    case "ansible":
        return ansible.New(config)
    default:
        return nil, fmt.Errorf("Password store '%s' not supported.", backend)
    }
}


package ansible


type Connection interface {
    open() (string, error)
}

type Ansible struct {
    connection Connection
    contents   map[string]string
}

func New(c map[string]interface{}) (*Ansible, error) {
    conn, err := NewConnection(c["ansible_path"].(string))
    if err != nil {
        return nil, err
    }

    // open connection, parse, etc...   

    a := &Ansible{
        connection: conn,
        contents:   parsedData,
    }

    return a, nil
}

所以这看起来不错,因为secrets 包不需要了解ansible 包依赖项(连接),并且工厂只是新建了带有一些配置数据的实例。但是,如果我需要模拟 Ansible 收到的 connection,似乎没有一个好的方法可以做到这一点(除非配置映射有一个名为 mock 的连接选项)

另一种选择是放弃工厂,只组装secrets包中的所有依赖项,例如:

package secrets

type PasswordStore interface {
    GetKey(key string) (string, error)
}

func New(backend string, config map[string]interface{}) (PasswordStore, error) {
    switch backend {
    case "ansible":
        return ansible.New(AnsibleConnection{}, config)
    default:
        return nil, fmt.Errorf("Password store '%s' not supported.", backend)
    }
}

package ansible


// same as before in this file, but with injected dependency ...

func New(connect Connection, c map[string]interface{}) (*Ansible, error) {
    conn, err := connect.NewConnection(c["ansible_path"].(string))
    if err != nil {
        return nil, err
    }

    // open connection, parse, etc...   

    a := &Ansible{
        connection: conn,
        contents:   parsedData,
    }

    return a, nil
}

现在注入了依赖项,但似乎secrets 需要了解每个实现的每个依赖项。

有没有更合乎逻辑的方式来构建这个,让secrets 知道的更少?还是顶级包通常会编排所有内容?

【问题讨论】:

    标签: go


    【解决方案1】:

    什么决定了后端是什么?这应该有助于指导你。我在一个项目中对多个数据库的支持做了类似的事情,我所做的基本上是:

    • config 包读取配置文件,这决定了正在使用的后端
    • store 包提供通用接口,并具有接受配置并返回实现的功能
    • server 包只引用接口
    • main 包读取配置,将其传递给 store 中的工厂函数,然后在创建时将结果注入服务器

    所以当我创建我的服务器(它实际上使用数据存储)时,我将配置传递给store 中的工厂函数,它返回一个接口,然后将其注入服务器。关于不同的具体实现,唯一需要知道的是暴露接口和工厂的同一个包; serverconfigmain 包将其视为黑匣子。

    【讨论】:

    • 感谢您的回复。是的,我在 ansible.new() 方法中有一个配置映射,其中包含所有配置数据(可能是从文件或其他东西中提取的)。我的问题是:如果你想模拟进入store 的东西,你有提供模拟的配置键吗?
    • 不,我完全跳过了工厂函数并将我的存根/模拟直接传递给被测单元。
    • @Adrian 和 store 工厂函数里面是什么?您是否根据您的配置设置了返回正确界面的条件?还是有更高级的解决方案?
    • @SergeyPodgornyy 正确,取决于配置;这是配置的目的,在运行时控制行为。我认为不需要“更高级”的解决方案。
    • @Adrian 我认为有一些灵活的解决方案。感谢您的回答
    猜你喜欢
    • 2018-03-03
    • 1970-01-01
    • 2017-11-03
    • 2021-06-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多