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)