Go语言精进之路读书笔记第44条——正确运用fake、stub和mock等辅助单元测试
44.1 fake:真实组件或服务的简化实现版替身
- fake测试就是指采用真实组件或服务的简化版实现作为替身,以满足被测代码的外部依赖需求。
- 使用fake替身进行测试的最常见理由是在测试环境无法构造被测代码所依赖的外部组件或服务,或者这些组件/服务有副作用。
type fakeOkMailer struct{}
func (m *fakeOkMailer) SendMail(subject string, dest string, body string) error {
return nil
}
func TestComposeAndSendOk(t *testing.T) {
m := &fakeOkMailer{}
mc := mailclient.New(m)
_, err := mc.ComposeAndSend("hello, fake test", []string{"xxx@example.com"}, "the test body")
if err != nil {
t.Errorf("want nil, got %v", err)
}
}
type fakeFailMailer struct{}
func (m *fakeFailMailer) SendMail(subject string, dest string, body string) error {
return fmt.Errorf("can not reach the mail server of dest [%s]", dest)
}
func TestComposeAndSendFail(t *testing.T) {
m := &fakeFailMailer{}
mc := mailclient.New(m)
_, err := mc.ComposeAndSend("hello, fake test", []string{"xxx@example.com"}, "the test body")
if err == nil {
t.Errorf("want non-nil, got nil")
}
}
44.2 stub:对返回结果有一定预设控制能力的替身
stub替身增强了对替身返回结果的间接控制能力,这种控制可以通过测试前对调用结果预设置来实现。
被测代码
type Weather struct {
City string `json:"city"`
Date string `json:"date"`
TemP string `json:"temP"`
Weather string `json:"weather"`
}
func GetWeatherInfo(addr string, city string) (*Weather, error) {
url := fmt.Sprintf("%s/weather?city=%s", addr, city)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http status code is %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var w Weather
err = json.Unmarshal(body, &w)
if err != nil {
return nil, err
}
return &w, nil
}
测试代码:我们使用httptest建立了一个天气服务器替身
var weatherResp = []Weather{
{
City: "nanning",
TemP: "26~33",
Weather: "rain",
Date: "05-04",
},
{
City: "guiyang",
TemP: "25~29",
Weather: "sunny",
Date: "05-04",
},
{
City: "tianjin",
TemP: "20~31",
Weather: "windy",
Date: "05-04",
},
}
func TestGetWeatherInfoOK(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var data []byte
if r.URL.EscapedPath() != "/weather" {
w.WriteHeader(http.StatusForbidden)
}
r.ParseForm()
city := r.Form.Get("city")
if city == "guiyang" {
data, _ = json.Marshal(&weatherResp[1])
}
if city == "tianjin" {
data, _ = json.Marshal(&weatherResp[2])
}
if city == "nanning" {
data, _ = json.Marshal(&weatherResp[0])
}
w.Write(data)
}))
defer ts.Close()
addr := ts.URL
city := "guiyang"
w, err := GetWeatherInfo(addr, city)
if err != nil {
t.Fatalf("want nil, got %v", err)
}
if w.City != city {
t.Errorf("want %s, got %s", city, w.City)
}
if w.Weather != "sunny" {
t.Errorf("want %s, got %s", "sunny", w.City)
}
}
44.3 mock:专用于行为观察和验证的替身
- 能力:mock能提供测试前的预设置返回结果能力,还可以对mock替身对象在测试过程中的行为进行观察和验证
- 局限性:mock只用于实现某接口的实现类型的替身;一般需要第三方框架支持(github.com/golang/mock/gomock)