Golang中如何模拟结构体方法进行测试

Golang中如何模拟结构体方法进行测试 我对在结构体方法中模拟HTTP请求有些困惑。给定以下示例,如何避免对真实服务器进行HTTP调用。

type Graph struct {
    ClientID string
    ClientSecret string
}

func (g *Graph) RefreshToken() err {
}

func (g *Graph) GetJSON(method string, url string) ([]byte, error) {
    // 刷新令牌
    g.RefreshToken()
    request, err := http.NewRequest(method, url, nil)
    if err != nil {
        return nil, err
    }
    response, err := Client.Do(request)
    if err != nil {
        return nil, err
    }

    defer response.Body.Close()
    body, err := ioutil.ReadAll(response.Body)

    if err != nil {
        return body, err
    }
    return body, nil
}

func (g *Graph) GetUsers() (map[string]interface{}, error) {
    result, err := g.GetJSON("GET", "https://example.com/users")
}

// main.go

client := Graph{`clientId`, `clientsecret`}
users := client.GetUsers()

更多关于Golang中如何模拟结构体方法进行测试的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

hollowaykeanho:

GetJSONFx

谢谢 @hollowaykeanho。这是标准做法吗?

更多关于Golang中如何模拟结构体方法进行测试的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一种常见做法,而非规则(标准)。

另一种策略取决于如何实现结构体初始化。对于这段代码片段,以下方式更有意义。该策略在使用 struct 之前,将 g.GetJSONFx 默认初始化为 getServerJSON,这样你就可以完全安全地跳过 GetJSON 内部的检查。

func main() {
    fmt.Println("hello world")
}

这样可行吗(由于你的代码不完整,尚未测试)?

type Graph struct {
	ClientID     string
	ClientSecret string

	GetJSONFx func(method string, url string) ([]byte, error)
}

func (g *Graph) GetJSON(method string, url string) ([]byte, error) {
	if g.GetJSONFx != nil {
		return g.GetJSONFx(method, url)
	}
	return g.getServerJSON(method, url)
}

func (g *Graph) getServerJSON(method string, url string) ([]byte, error) {
	// Refresh token
	g.RefreshToken()
	request, err := http.NewRequest(method, url, nil)
	if err != nil {
		return nil, err
	}
	response, err := Client.Do(request)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)

	if err != nil {
		return body, err
	}
	return body, nil
}

func main() {
	g := &Graph{
		GetJSONFx = func(method string, url string) ([]byte, error) {
				... // your custom function
		},
	}
	
	g.GetJSON(...)
}

还有其他类似的方法,但由于你硬编码了服务器端代码,这种策略更有意义。

@BastinRobin,等等,我之前没注意到你只是用于测试。对于测试,请使用:

https://pkg.go.dev/net/http/httptest

你可以模拟请求和响应,并将其传入你的方法。

上述策略是针对运行时模拟(如应用程序模拟/变更)。除非你的应用程序正在使用它,否则请避免在测试中使用。


更新:收回我上面的说法:你可以使用上述策略。除非 http 是外部的,只有那时你才可以使用 httptest。我之前的建议会是更好的选择。

抱歉,我过去连续工作了12个小时。

如果你想提高 GetJSON 方法的效率,可以做一些类似 init() 方法的事情。众多例子中的一个:

type Graph struct {
	ClientID     string
	ClientSecret string

	GetJSONFx func(method string, url string) ([]byte, error)
}

func (g *Graph) Init() (err error) {
	...
	g.GetJSONFx = g.getServerJSON
	...
}

func (g *Graph) GetJSON(method string, url string) ([]byte, error) {
	return g.getServerJSON(method, url)
}

func main() {
	g := &Graph{}
	err := g.Init()
	if err != nil {
		// handle error
		return nil
	}

	// mock it when needed
	g.GetJSONFx = func(method string, url string) ([]byte, error) {
				... // your custom function
	}

	for {
		d, err := g.GetJSON(...)  // calls does not need to check for GetJSONFx during work everytime
	}
}

在Golang中模拟结构体方法进行测试,通常使用接口和依赖注入。以下是针对你的代码示例的解决方案:

首先,定义接口来抽象HTTP客户端:

type HTTPClient interface {
    Do(req *http.Request) (*http.Response, error)
}

type Graph struct {
    ClientID     string
    ClientSecret string
    Client       HTTPClient  // 使用接口类型
}

修改GetJSON方法使用注入的客户端:

func (g *Graph) GetJSON(method string, url string) ([]byte, error) {
    g.RefreshToken()
    request, err := http.NewRequest(method, url, nil)
    if err != nil {
        return nil, err
    }
    
    response, err := g.Client.Do(request)  // 使用注入的客户端
    if err != nil {
        return nil, err
    }

    defer response.Body.Close()
    body, err := ioutil.ReadAll(response.Body)
    if err != nil {
        return body, err
    }
    return body, nil
}

创建模拟HTTP客户端进行测试:

type MockHTTPClient struct {
    DoFunc func(req *http.Request) (*http.Response, error)
}

func (m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) {
    if m.DoFunc != nil {
        return m.DoFunc(req)
    }
    return nil, nil
}

func TestGetUsers(t *testing.T) {
    mockClient := &MockHTTPClient{
        DoFunc: func(req *http.Request) (*http.Response, error) {
            // 验证请求参数
            if req.URL.String() != "https://example.com/users" {
                t.Errorf("期望URL: https://example.com/users, 实际: %s", req.URL.String())
            }
            
            // 返回模拟响应
            body := `{"users": [{"id": 1, "name": "test"}]}`
            return &http.Response{
                StatusCode: 200,
                Body:       ioutil.NopCloser(strings.NewReader(body)),
            }, nil
        },
    }
    
    graph := &Graph{
        ClientID:     "test-id",
        ClientSecret: "test-secret",
        Client:       mockClient,
    }
    
    users, err := graph.GetUsers()
    if err != nil {
        t.Errorf("GetUsers失败: %v", err)
    }
    
    // 验证结果
    if users == nil {
        t.Error("期望返回用户数据")
    }
}

对于RefreshToken方法的模拟,可以使用类似的方法:

type TokenRefresher interface {
    RefreshToken() error
}

type MockGraph struct {
    Graph
    RefreshTokenFunc func() error
}

func (m *MockGraph) RefreshToken() error {
    if m.RefreshTokenFunc != nil {
        return m.RefreshTokenFunc()
    }
    return nil
}

func TestGetJSON_WithMockRefresh(t *testing.T) {
    refreshCalled := false
    mockGraph := &MockGraph{
        RefreshTokenFunc: func() error {
            refreshCalled = true
            return nil
        },
    }
    
    // 设置模拟HTTP客户端
    mockGraph.Client = &MockHTTPClient{
        DoFunc: func(req *http.Request) (*http.Response, error) {
            return &http.Response{
                StatusCode: 200,
                Body:       ioutil.NopCloser(strings.NewReader(`{}`)),
            }, nil
        },
    }
    
    _, err := mockGraph.GetJSON("GET", "https://example.com/test")
    if err != nil {
        t.Errorf("GetJSON失败: %v", err)
    }
    
    if !refreshCalled {
        t.Error("RefreshToken方法未被调用")
    }
}

在生产代码中初始化Graph:

// main.go
func main() {
    client := &Graph{
        ClientID:     "clientId",
        ClientSecret: "clientsecret",
        Client:       &http.Client{},  // 使用真实的HTTP客户端
    }
    
    users, err := client.GetUsers()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(users)
}

这种方法通过接口实现了依赖注入,使得在测试时可以轻松替换真实的HTTP客户端和RefreshToken方法,避免了对外部服务的真实调用。

回到顶部