首页 > 解决方案 > 如何提高 go 库方法的可测试性

问题描述

我正在编写一些使用名为Vault的库的代码。在这个库中,我们有一个Client. 我的代码利用了这一点Client,但我希望能够轻松测试使用它的代码。我只使用了库中的几个方法,所以我最终创建了一个接口:

type VaultClient interface {
    Logical() *api.Logical
    SetToken(v string)
    NewLifetimeWatcher(i *api.LifetimeWatcherInput) (*api.LifetimeWatcher, error)
}

现在,如果我的代码指向这一点,那么interface一切都很容易测试。除了让我们看看Logical()方法。它返回一个struct here。我的问题是,这个Logical结构上还有一些方法可以让你Read,,,Write例如:

func (c *Logical) Read(path string) (*Secret, error) {
    return c.ReadWithData(path, nil)
}

这些也被用于我的项目中,以执行以下操作:

{{ VaultClient defined above }}.Logical().Write("something", something)

这是问题所在。Logical从调用返回的方法.Logical()有一个我无法模拟.Write的方法。.Read我不希望这些方法中的所有逻辑都在我的测试中运行。

理想情况下,我希望能够做类似于我上面所做的事情并创建一个interfacefor Logical。我对 Golang 比较陌生,但我正在努力寻找最好的方法。据我所知,这是不可能的。嵌入不像继承那样工作,所以看起来我必须返回一个Logical. 这使得我的代码无法像我想要的那样简单地测试,因为 aLogical的方法中的所有逻辑都不能被模拟。

我在这里有点不知所措。我已经在谷歌上搜索了这个问题的答案,但没有人谈论过这种情况。他们只走我interface为客户最初的目标。

这是一个常见的场景吗?我用过的其他库不返回structs like Logical。相反,它们通常只返回一个struct包含数据且没有方法的平淡无奇的内容。

标签: gomocking

解决方案


package usecasevaultclient

// usecase.go
type VaultClient interface {
    Logical() *api.Logical
    SetToken(v string)
    NewLifetimeWatcher(i *api.LifetimeWatcherInput) (*api.LifetimeWatcher, error)
}

type vaultClient struct {
   repo RepoVaultClient
}

// create new injection
func NewVaultClient(repo RepoVaultClient) VaultClient {
  return &vaultClient{repo}
}

func(u *vaultClient) Logical() *api.Logical {
 // do your logic and call the repo of
   u.repo.ReadData()
   u.repo.WriteData()
}
func(u *vaultClient) SetToken(v string) {}
func(u *vaultClient) NewLifetimeWatcher(i *api.LifetimeWatcherInput) (*api.LifetimeWatcher, error)

// interfaces.go
type RepoVaultClient interface {
   ReadData() error
   WriteData() error
}

// repo_vaultclient_mock.go
import "github.com/stretchr/testify/mock"

type MockRepoVaultClient struct {
   mock.Mock
}

func (m *MockRepoVaultClient) ReadData() error {
      args := m.Called()
      return args.Error(0)
}

func (m *MockRepoVaultClient) WriteData() error {
      args := m.Called()
      return args.Error(0)
}


// vaultClient_test.go

func TestLogicalShouldBeSuccess(t *testing.T) {
  mockRepoVaultClient = &MockRepoVaultClient{}

  useCase := NewVaultClient(mockRepoVaultClient)

  mockRepoVaultClient.On("ReadData").Return(nil)
  mockRepoVaultClient.On("WriteData").Return(nil)

  // your logics gonna make this response as actual what u implemented
  response := useCase.Logical()

  assert.Equal(t, expected, response)
}

如果要测试 Logical 的接口,则需要使用 testify/mock 模拟 ReadData 和 WriteData ,以便您可以定义这些方法的返回响应,并且可以在调用接口的新注入后进行比较


推荐阅读