Goの標準のHTTPサーバの機能を使ったアプリケーションのテスト
Goの標準のHTTPサーバの機能を使ったアプリケーションのテストをするにはどうするか。というテーマが少なそうだったので、ちょっと調べてみました。
##サンプルコードその1
サーバ側のコードもテストコードもダミーなのですが、標準的に使うには以下のようにするようです。(Goのnet/httpパッケージにも実際に使われています)
ポイントは、httptest.NewServer([ハンドラ])を呼び出すところです。
後は、普通にリクエストを送信して下さい。
// 本来の処理のダミーその1
var sampleHandler = http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello HTTP Test")
})
func TestNormal(t *testing.T) {
ts := httptest.NewServer(sampleHandler)
defer ts.Close()
// リクエストの送信先はテストサーバのURLへ。
r, err := http.Get(ts.URL)
if err != nil {
t.Fatalf("Error by http.Get(). %v", err)
}
data, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("Error by ioutil.ReadAll(). %v", err)
}
if "Hello HTTP Test" != string(data) {
t.Fatalf("Data Error. %v", string(data))
}
}
##サンプルコードその2
httpsのテストがしたい場合はhttptest.NewTLSServer()に変えます。実際にts.URLの値は、httpsになっていますので、Clientのオブジェクトも標準のものではなく、TLSClientConfigを設定する必要があります。
func TestTLS(t *testing.T) {
ts := httptest.NewTLSServer(sampleHandler)
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
c := &http.Client{Transport: tr}
r, err := c.Get(ts.URL)
if err != nil {
t.Fatalf("Error by http.Get(). %v", err)
}
data, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("Error by ioutil.ReadAll(). %v", err)
}
if "Hello HTTP Test" != string(data) {
t.Fatalf("Data Error. %v", string(data))
}
}
##サンプルコードその3
例えば、「User-AgentがAndroid以外の場合は404を返す。」と言った感じのテストです。
(本当は、もっとたくさんのUser-Agentをテストするのかもしれませんが…)
// 本来の処理のダミーその2
var sample2Handler = http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
ua := r.Header.Get("User-Agent")
if !strings.Contains(ua, "Android") {
http.Error(w, "Not Found.", http.StatusNotFound)
return
}
fmt.Fprintf(w, "Hello HTTP Test")
})
func TestAndroid(t *testing.T) {
var requests [2]*http.Request
var err error
ts := httptest.NewServer(sample2Handler)
defer ts.Close()
requests[0], err = http.NewRequest("GET", ts.URL, nil)
if err != nil {
t.Errorf("NewRequest[0] Error. %v", err)
}
requests[0].Header.Add("User-Agent", "Mozilla/5.0 (iPad; CPU OS 8_1_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/39.0.2171.50 Mobile/12B440 Safari/600.1.4")
requests[1], err = http.NewRequest("GET", ts.URL, nil)
if err != nil {
t.Errorf("NewRequest[1] Error. %v", err)
}
requests[1].Header.Add("User-Agent", "Mozilla/5.0 (Linux; Android 4.4.2; 302KC Build/101.0.2c00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36")
c := http.DefaultClient
for pos, req := range requests {
r, err := c.Do(req)
if err != nil {
t.Fatalf("Error by http.Get(). %v", err)
}
data, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("Error by ioutil.ReadAll(). %v", err)
}
if r.StatusCode == 200 {
if "Hello HTTP Test" != string(data) {
t.Fatalf("Data Error. %v", string(data))
}
} else {
if r.StatusCode != 404 {
t.Fatalf("Status Error %d", r.StatusCode)
}
if "Not Found.\n" != string(data) {
t.Fatalf("Data Error. %v", string(data))
}
if pos != 1 {
t.Fatalf("Request Error. %d", pos)
}
}
}
}
### 一つ問題が。
書き方がおかしいのか、ハンドラは間違いなく、404を返している(http.Error()を呼び出している)にもかかわらず、なぜか、レスポンスが全て200になる現象がでました。これでは、正常系の確認ができても異常系のテストが正しくできないということになるので、少し不便です。(理由がよくわからなかったので、すでに使っている方は教えて下さい)
2014/12/28追記:
サンプルコードのハンドラの呼び出しがおかしいのと、bodyのテストが間違っていたので修正しました。ちゃんと404が返ってきました。
GDG Cafe #1beta2でご指摘いただきました。ありがとうございました。
本題に戻って、これはGAE/Goには使いづらい(?)
GAE/Goでもnet/http/httptestを使ってみようと思うと、上の書き方はちょっと使えなさそうな予感がする。ので、GAE/Go、普通のGoの両方で使える、リクエストとレスポンスを自作する方法を紹介しておきます。
サンプルコード(その4)
func TestAndroidNoServer(t *testing.T) {
var requests [2]*http.Request
var err error
requests[0], err = http.NewRequest("GET", "/hoge", nil)
if err != nil {
t.Errorf("NewRequest[0] Error. %v", err)
}
requests[0].Header.Add("User-Agent", "Mozilla/5.0 (iPad; CPU OS 8_1_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/39.0.2171.50 Mobile/12B440 Safari/600.1.4")
requests[1], err = http.NewRequest("GET", "/hoge", nil)
if err != nil {
t.Errorf("NewRequest[1] Error. %v", err)
}
requests[1].Header.Add("User-Agent", "Mozilla/5.0 (Linux; Android 4.4.2; 302KC Build/101.0.2c00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36")
for pos, req := range requests {
r := httptest.NewRecorder()
sample2Handler(r, req)
data, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("Error by ioutil.ReadAll(). %v", err)
}
if r.Code == 200 {
if "Hello HTTP Test" != string(data) {
t.Fatalf("Data Error. %v", string(data))
}
} else {
if r.Code != 404 {
t.Fatalf("Status Error %d", r.Code)
}
// httptest.ResponseRecorderは改行コードが付いてしまう
if "Not Found.\n" != string(data) {
t.Fatalf("Data Error. %v", string(data))
}
if pos != 0 {
t.Fatalf("Request Error. %d", pos)
}
}
}
}
ソースコード全体
ソースコードはgithubに公開してありますのでそちらをごらんください。
https://github.com/tyokoyama/golangcafe/tree/master/httptestsample
結論
- もしHTTPのテストでローカルサーバを起動する処理(ListenAndServe()を呼び出す)を書かれている方はこちらの方法に書きなおした方が良いと思います。
- 実際に導入してみないとわからないので、導入してみてくださいw
Disclaimer
- この記事は個人的なものです。私の雇用者とは全く関係はありません。(一応つけておきます)