Go语言中如何通过依赖注入Mock getData函数并完成单元测试
在Go中通过依赖注入实现生产与测试环境的URL切换及单元测试
你的思路完全正确——依赖注入确实是解决这类环境隔离测试问题的核心方案。咱们把现有代码调整得更规范、更易维护,同时完美满足生产用真实URL、测试用httptest模拟服务的需求。
一、先修正生产代码的潜在问题
先把现有代码里的小瑕疵(比如资源泄漏、可读性差)解决掉:
1. 优化iData接口的可读性
原来的getData()返回无意义的字符串数组,靠索引取参数太容易出错。咱们改成更清晰的接口定义:
// 替换原有的iData接口,明确返回业务所需的配置信息 type iData interface { GetAuth() (username, password string) GetFullURL() string } // 生产环境的配置实现 type ProdData struct { Username string Password string BaseURL string Path string } func (p ProdData) GetAuth() (string, string) { return p.Username, p.Password } func (p ProdData) GetFullURL() string { return p.BaseURL + p.Path }
2. 修复HTTP请求的资源泄漏
原来的defer res.Body.Close()放在了读取响应之后,若读取失败会导致资源泄漏,咱们提前到获取响应后:
func (i impl) HTTPRequest(username, password, method, url string) ([]byte, error) { req, err := http.NewRequest(method, url, nil) if err != nil { return nil, err } req.SetBasicAuth(username, password) res, err := i.client.Do(req) if err != nil { return nil, err } // 提前defer,确保无论后续是否出错,响应体都会被关闭 defer res.Body.Close() token, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } fmt.Println("request success") return token, nil }
3. 调整业务方法的可测试性
把caller改成返回错误,方便测试时断言请求结果:
func (s *ServiceInfo) caller() error { username, password := s.data.GetAuth() fullURL := s.data.GetFullURL() // 把HTTP请求的错误返回,便于测试断言 _, err := s.req.HTTPRequest(username, password, "POST", fullURL) return err }
二、实现测试用的Mock配置
定义一个Mock版的配置结构体,实现iData接口,返回测试服务器的URL和预设的认证信息:
// 测试环境的Mock配置 type MockData struct { TestURL string Username string Password string } func (m MockData) GetAuth() (string, string) { return m.Username, m.Password } func (m MockData) GetFullURL() string { return m.TestURL }
三、完善单元测试代码
用httptest.NewServer模拟真实HTTP服务,验证请求的认证信息是否正确:
import ( "net/http" "net/http/httptest" "github.com/onsi/gomega" "testing" ) func TestServiceCaller(t *testing.T) { gomega.RegisterTestingT(t) // 启动测试服务器,验证请求的BasicAuth信息 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { u, p, ok := r.BasicAuth() gomega.Expect(ok).To(gomega.Equal(true)) gomega.Expect(u).To(gomega.Equal("test-user")) gomega.Expect(p).To(gomega.Equal("test-pass")) // 返回模拟的响应内容 w.WriteHeader(http.StatusOK) w.Write([]byte(`{"access_token":"test-token"}`)) })) defer testServer.Close() // 测试结束后关闭服务器 // 注入测试依赖 httpClient := &http.Client{} mockConfig := MockData{ TestURL: testServer.URL, Username: "test-user", Password: "test-pass", } service := NewServiceInfo(impl{client: httpClient}, mockConfig) // 调用业务方法并断言无错误 err := service.caller() gomega.Expect(err).To(gomega.BeNil()) }
四、生产环境的初始化代码
生产环境只需注入真实的配置实例即可:
func main() { prodConfig := ProdData{ Username: "real-user", Password: "real-pass", BaseURL: "http://www.mocky.io/v2/5c20eccc2e00005c001e0c84", Path: "/oauth/token?grant_type=client_credentials", } httpClient := &http.Client{} service := NewServiceInfo(impl{client: httpClient}, prodConfig) if err := service.caller(); err != nil { fmt.Printf("request failed: %v\n", err) } }
核心逻辑总结
- 依赖注入:通过将
requester和iData接口作为ServiceInfo的依赖,而非硬编码实现,让我们能在测试时轻松替换成Mock版本。 - 接口隔离:
iData接口负责提供业务配置,生产和测试环境分别实现该接口,完美实现环境隔离。 - 可控测试:用
httptest.NewServer模拟真实服务,既能控制响应内容,又能验证请求的正确性(比如认证信息、请求方法)。
内容的提问来源于stack exchange,提问作者Rayn D
相关产品推荐
相关产品推荐

