郎朗坤
您的测试用例不应超出实现范围。我特别指的是空对非空输入或任何类型的输入。让我们看一下您要测试的代码:func (c *Client) GetPost(id string) (*Post, error) { req := graphql.NewRequest(` query($id: String!) { getPost(id: $id) { id title } } `) req.Var("id", id) var resp getPostResponse if err := c.gcl.Run(ctx, req, &resp); err != nil { return nil, err } return resp.Post, nil}上面的实现中没有任何东西基于id参数值做任何事情,因此在你的这段代码的测试中没有任何东西应该真正关心传入的输入,如果它与实现无关,它也应该与测试无关。您GetPost基本上有两个基于单个因素的代码分支,即返回err变量的“零”。这意味着就您的实现而言,根据返回的err值,只有两种可能的结果,Run因此应该只有两个测试用例,第三个或第四个测试用例只是一个变体,如果不是一个完整的副本, 前两个。你的测试客户端也在做一些不必要的事情,主要的是它的名字,即你所拥有的不是一个模拟,所以调用它是没有帮助的。模拟通常不仅仅是返回预定义的值,它们确保方法被调用,以预期的顺序和预期的参数等。实际上你根本不需要模拟,所以这是一件好事'语气。考虑到这一点,我建议您对测试客户端执行以下操作。type testGraphqlClient struct { resp interface{} // non-pointer value of the desired response, or nil err error // the error to be returned by Run, or nil}func (g testGraphqlClient) Run(_ context.Context, _ *graphql.Request, resp interface{}) error { if g.err != nil { return g.err } if g.resp != nil { // use reflection to set the passed in response value // (i haven't tested this so there may be a bug or two) reflect.ValueOf(resp).Elem().Set(reflect.ValueOf(g.resp)) } return nil}...这是必要的测试用例,所有两个:func TestClient_GetPost(t *testing.T) { tests := []struct { name string post *Post err error client testGraphqlClient }{{ name: "return error from client", err: errors.New("bad input"), client: testGraphqlClient{err: errors.New("bad input")}, }, { name: "return post from client", post: &Post{id: aws.String("123")}, client: testGraphqlClient{resp: getPostResponse{Post: &Post{id: aws.String("123")}}}, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { client := Client{gql: tt.client} post, err := client.GetPost("whatever") if !cmp.Equal(err, tt.err) { t.Errorf("got error=%v want error=%v", err, tt.err) } if !cmp.Equal(post, tt.post) { t.Errorf("got post=%v want post=%v", post, tt.post) } }) }}...这里有一些重复,需要拼出postanderr两次,但与更复杂/复杂的测试设置相比,这是一个很小的代价,它将从测试用例的预期输出字段填充测试客户端.附录:如果您要GetPost以这样一种方式进行更新,即在向 graphql 发送请求之前检查空 id 并返回错误,那么您的初始设置将更有意义:func (c *Client) GetPost(id string) (*Post, error) { if id == "" { return nil, errors.New("empty id") } req := graphql.NewRequest(` query($id: String!) { getPost(id: $id) { id title } } `) req.Var("id", id) var resp getPostResponse if err := c.gcl.Run(ctx, req, &resp); err != nil { return nil, err } return resp.Post, nil}...并相应地更新测试用例:func TestClient_GetPost(t *testing.T) { tests := []struct { name string id string post *Post err error client testGraphqlClient }{{ name: "return empty id error", id: "", err: errors.New("empty id"), client: testGraphqlClient{}, }, { name: "return error from client", id: "nonemptyid", err: errors.New("bad input"), client: testGraphqlClient{err: errors.New("bad input")}, }, { name: "return post from client", id: "nonemptyid", post: &Post{id: aws.String("123")}, client: testGraphqlClient{resp: getPostResponse{Post: &Post{id: aws.String("123")}}}, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { client := Client{gql: tt.client} post, err := client.GetPost(tt.id) if !cmp.Equal(err, tt.err) { t.Errorf("got error=%v want error=%v", err, tt.err) } if !cmp.Equal(post, tt.post) { t.Errorf("got post=%v want post=%v", post, tt.post) } }) }}