你如何存根调用 GitHub 进行测试?
how can you stub calls to GitHub for testing?
我需要使用 go-github 创建一个 Pull Request 评论,我的代码可以工作,但现在我想为它编写测试(是的,我知道测试应该放在第一位),这样我就不会在测试期间实际调用真正的 GitHub 服务。
我已经阅读了 3 篇关于 golang stubbing 和 mocking 的博客,但是,作为 golang 的新手,尽管 this discussion on go-github issues 我还是有点迷茫。比如我写了下面这个函数:
// this is my function
func GetClient(token string, url string) (*github.Client, context.Context, error) {
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
tc := oauth2.NewClient(ctx, ts)
client, err := github.NewEnterpriseClient(url, url, tc)
if err != nil {
fmt.Printf("error creating github client: %q", err)
return nil, nil, err
}
return client, ctx, nil
}
我怎么能存根呢?
同样,我有这个:
func GetPRComments(ctx context.Context, client *github.Client) ([]*github.IssueComment, *github.Response, error) {
opts := &github.IssueListCommentsOptions{
ListOptions: github.ListOptions{
Page: 1,
PerPage: 30,
},
}
githubPrNumber, err := strconv.Atoi(os.Getenv("GITHUB_PR_NUMBER"))
if err != nil || githubPrNumber == 0 {
panic("error: GITHUB_PR_NUMBER is not numeric or empty")
}
// use Issues API for PR comments since GitHub docs say "This may seem counterintuitive... but a...Pull Request is just an Issue with code"
comments, response, err := client.Issues.ListComments(
ctx,
os.Getenv("GITHUB_OWNER"),
os.Getenv("GITHUB_REPO"),
githubPrNumber,
opts)
if err != nil {
return nil, nil, err
}
return comments, response, nil
}
我应该如何存根?
我的想法是通过首先创建我自己的结构来使用依赖注入,但我不确定如何,所以目前我有这个:
func TestGetClient(t *testing.T) {
client, ctx, err := GetClient(os.Getenv("GITHUB_TOKEN"), "https://example.com/api/v3/")
c, r, err := GetPRComments(ctx, client)
...
}
我将从界面开始:
type ClientProvider interface {
GetClient(token string, url string) (*github.Client, context.Context, error)
}
在测试需要调用 GetClient
的单元时,请确保您依赖于您的 ClientProvider
接口:
func YourFunctionThatNeedsAClient(clientProvider ClientProvider) error {
// build you token and url
// get a github client
client, ctx, err := clientProvider.GetClient(token, url)
// do stuff with the client
return nil
}
现在在你的测试中,你可以像这样构造一个存根:
// A mock/stub client provider, set the client func in your test to mock the behavior
type MockClientProvider struct {
GetClientFunc func(string, string) (*github.Client, context.Context, error)
}
// This will establish for the compiler that MockClientProvider can be used as the interface you created
func (provider *MockClientProvider) GetClient(token string, url string) (*github.Client, context.Context, error) {
return provider.GetClientFunc(token, url)
}
// Your unit test
func TestYourFunctionThatNeedsAClient(t *testing.T) {
mockGetClientFunc := func(token string, url string) (*github.Client, context.Context, error) {
// do your setup here
return nil, nil, nil // return something better than this
}
mockClientProvider := &MockClientProvider{GetClientFunc: mockGetClientFunc}
// Run your test
err := YourFunctionThatNeedsAClient(mockClientProvider)
// Assert your result
}
这些想法不是我自己的,我是从前人那里借来的; Mat Ryer 在关于“地道的 golang”的精彩视频中提出了这个(和其他想法)。
如果你想存根 github 客户端本身,可以使用类似的方法,如果 github.Client 是一个结构,你可以用一个接口隐藏它。如果已经是接口,上面的方法直接生效
我需要使用 go-github 创建一个 Pull Request 评论,我的代码可以工作,但现在我想为它编写测试(是的,我知道测试应该放在第一位),这样我就不会在测试期间实际调用真正的 GitHub 服务。
我已经阅读了 3 篇关于 golang stubbing 和 mocking 的博客,但是,作为 golang 的新手,尽管 this discussion on go-github issues 我还是有点迷茫。比如我写了下面这个函数:
// this is my function
func GetClient(token string, url string) (*github.Client, context.Context, error) {
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
tc := oauth2.NewClient(ctx, ts)
client, err := github.NewEnterpriseClient(url, url, tc)
if err != nil {
fmt.Printf("error creating github client: %q", err)
return nil, nil, err
}
return client, ctx, nil
}
我怎么能存根呢?
同样,我有这个:
func GetPRComments(ctx context.Context, client *github.Client) ([]*github.IssueComment, *github.Response, error) {
opts := &github.IssueListCommentsOptions{
ListOptions: github.ListOptions{
Page: 1,
PerPage: 30,
},
}
githubPrNumber, err := strconv.Atoi(os.Getenv("GITHUB_PR_NUMBER"))
if err != nil || githubPrNumber == 0 {
panic("error: GITHUB_PR_NUMBER is not numeric or empty")
}
// use Issues API for PR comments since GitHub docs say "This may seem counterintuitive... but a...Pull Request is just an Issue with code"
comments, response, err := client.Issues.ListComments(
ctx,
os.Getenv("GITHUB_OWNER"),
os.Getenv("GITHUB_REPO"),
githubPrNumber,
opts)
if err != nil {
return nil, nil, err
}
return comments, response, nil
}
我应该如何存根?
我的想法是通过首先创建我自己的结构来使用依赖注入,但我不确定如何,所以目前我有这个:
func TestGetClient(t *testing.T) {
client, ctx, err := GetClient(os.Getenv("GITHUB_TOKEN"), "https://example.com/api/v3/")
c, r, err := GetPRComments(ctx, client)
...
}
我将从界面开始:
type ClientProvider interface {
GetClient(token string, url string) (*github.Client, context.Context, error)
}
在测试需要调用 GetClient
的单元时,请确保您依赖于您的 ClientProvider
接口:
func YourFunctionThatNeedsAClient(clientProvider ClientProvider) error {
// build you token and url
// get a github client
client, ctx, err := clientProvider.GetClient(token, url)
// do stuff with the client
return nil
}
现在在你的测试中,你可以像这样构造一个存根:
// A mock/stub client provider, set the client func in your test to mock the behavior
type MockClientProvider struct {
GetClientFunc func(string, string) (*github.Client, context.Context, error)
}
// This will establish for the compiler that MockClientProvider can be used as the interface you created
func (provider *MockClientProvider) GetClient(token string, url string) (*github.Client, context.Context, error) {
return provider.GetClientFunc(token, url)
}
// Your unit test
func TestYourFunctionThatNeedsAClient(t *testing.T) {
mockGetClientFunc := func(token string, url string) (*github.Client, context.Context, error) {
// do your setup here
return nil, nil, nil // return something better than this
}
mockClientProvider := &MockClientProvider{GetClientFunc: mockGetClientFunc}
// Run your test
err := YourFunctionThatNeedsAClient(mockClientProvider)
// Assert your result
}
这些想法不是我自己的,我是从前人那里借来的; Mat Ryer 在关于“地道的 golang”的精彩视频中提出了这个(和其他想法)。
如果你想存根 github 客户端本身,可以使用类似的方法,如果 github.Client 是一个结构,你可以用一个接口隐藏它。如果已经是接口,上面的方法直接生效