Original implementation:
type SingleItem struct { Field string `json:"field"` Hour int `json:"hour"` Minute int `json:"minute"` ItemCode string `json:"item_code"` Price float64 `json:"price"` Quantity int `json:"qty"` } type RawItems struct { Items []SingleItem `json:"items"` TotalRecordCount int `json:"total_record_count"` Start int `json:"start"` } type Calculator struct{} func (c *Calculator) SomeComplexAggregationFunction(startDate, endDate time.Time, field string) (float64, error) { convertedStartTime := startDate.Format("2006-02-01") convertedEndTime := startDate.Format("2006-02-01") rawResp, err := http.Get(fmt.Sprintf("http://example-data-server/api/data-archive/v1/retail?field=%v&start-date=%v&end-date=%v", field, convertedStartTime, convertedEndTime)) if err != nil { return 0.0, err } if rawResp.StatusCode != http.StatusOK { return 0.0, fmt.Errorf("unexpected status code") } raw, err := io.ReadAll(rawResp.Body) if err != nil { return 0.0, err } var items RawItems err = json.Unmarshal(raw, &items) if err != nil { return 0, err } // Pretend this is some complex calculation summer := 0.0 for k, v := range items.Items { fmt.Printf("processing current item: %v", k) summer = float64(v.Quantity)*v.Price + summer } return summer, nil }
Refactored implementation:
type V1InternalEndpoint struct{} func (e *V1InternalEndpoint) Retrieve(startDate, endDate time.Time, field string) ([]SingleItem, error) { convertedStartTime := startDate.Format("2006-02-01") convertedEndTime := startDate.Format("2006-02-01") rawResp, err := http.Get(fmt.Sprintf("http://example-data-server/api/data-archive/v1/retail?field=%v&start-date=%v&end-date=%v", field, convertedStartTime, convertedEndTime)) if err != nil { return []SingleItem{}, err } if rawResp.StatusCode != http.StatusOK { return []SingleItem{}, fmt.Errorf("unexpected status code") } raw, err := io.ReadAll(rawResp.Body) if err != nil { return []SingleItem{}, err } var items RawItems err = json.Unmarshal(raw, &items) if err != nil { return []SingleItem{}, err } return items.Items, nil } type DataRetriever interface { Retrieve(startDate, endDate time.Time, field string) ([]SingleItem, error) } type Calculator struct { d DataRetriever } func (c *Calculator) SomeComplexAggregationFunction(startDate, endDate time.Time, field string) (float64, error) { items, err := c.d.Retrieve(startDate, endDate, field) if err != nil { return 0, err } // Pretend this is some complex calculation summer := 0.0 for k, v := range items { fmt.Printf("processing current item: %v", k) summer = float64(v.Quantity)*v.Price + summer } return summer, nil }
Notice how much simpler the function becomes as we move the external call out of the function where we implement the logic which would contain our main logic. The call to retrieve data could be a real piece of code that actually does external calls and retrieve data via JSON or thrift, or GRPC protocols. It could also be a piece of code that provides fake data that we could test our application against.
Seeing that our code now relies on DataRetriever interface, we would need to build our mock against it and ensure that it follows that function signature.
type FakeDataRetriever struct{} func (f *FakeDataRetriever) Retrieve(startDate, endDate time.Time, field string) ([]SingleItem, error) { if field == "receipts" { return []SingleItem{SingleItem{Price: 1.1, Quantity: 2}}, nil } return []SingleItem{}, fmt.Errorf("no data available") }
We can specify what kind of data that might be returned from the external calls:
• Maybe an array of 1,000 items could be returned
• Maybe include invalid data
You can probably extend this concept further, such as database calls; application calls to queue systems such as Kafka and Nats; or application calls to caches such as Redis, and so on. All of this can be mocked and have unit tests be run against the logic that we write up—it is just that it takes a bit of effort to maintain such mocking code.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律