17
The Go gopher was designed by Renée French. The gopher stickers was made by Takuya Ueda. Licensed under the Creative Commons 3.0 Attributions license. Go Friday傑作選 @golang.tokyo #9 2017/09/29

Go Friday 傑作選

Embed Size (px)

Citation preview

Page 1: Go Friday 傑作選

The Go gopher was designed by Renée French.The gopher stickers was made by Takuya Ueda.Licensed under the Creative Commons 3.0 Attributions license.

Go Friday傑作選

@golang.tokyo #92017/09/29

Page 2: Go Friday 傑作選

自己紹介

上田拓也@tenntenn

2

所属

コミュニティ活動

&

Go ビギナーGo Conference

Page 3: Go Friday 傑作選

Go Friday■ GoやGCPに関するソウゾウの社内勉強会

● 社外で得た知見の共有

○ 海外カンファレンスについての共有

○ コミュニティで得た知識の共有

● 社内の各プロダクト開発で得た知見の共有

○ 設計の方針や実装方法の共有

○ 共通ライブラリの共有

● 社外へのアウトプットの練習

○ 発表練習

○ イベント登壇のネタ出し

○ 社内ライブラリのOSS化の相談

参考:https://codeiq.jp/magazine/2017/04/50250/

Page 4: Go Friday 傑作選

switchを使おう

const (StatusA = 0StatusB = 1StatusC = 0 // miss

)switch s {

case StatusA:fmt.Println("A")

case StatusB:fmt.Println("B")

case StatusC: // errorfmt.Println("C")

}

Page 5: Go Friday 傑作選

ついにIsTemporaryを実装したぞう

■ 用途● 一時的なエラーを表現する● タスクキューのエラーでリトライすればそのうち直るもの● エラーログとして書き出されるとslackに通知されて困るもの

type temporary interface { Temporary() bool}

func IsTemporary(err error) bool { te, ok := err.(temporary) return ok && te.Temporary()}

参考:http://deeeet.com/writing/2016/04/25/go-pkg-errors/

Page 6: Go Friday 傑作選

@mitchellh のテストの話よい

■ Advanced Testing with Go● https://speakerdeck.com/mitchellh/advanced-testing-with-go● テーブルドリブンテスト

● サブテスト

● ヘルパー関数

Page 7: Go Friday 傑作選

テーブル駆動テスト

■ テスト対象のデータを羅列してテストする

参考:https://github.com/golang/go/wiki/TableDrivenTests

var flagtests = []struct {in stringout string

}{{"%a", "[%a]"}, {"%-a", "[%-a]"}, {"%+a", "[%+a]"},{"%#a", "[%#a]"}, {"% a", "[% a]"},

}func TestFlagParser(t *testing.T) {

var flagprinter flagPrinterfor _, tt := range flagtests {

s := Sprintf(tt.in, &flagprinter)if s != tt.out {

t.Errorf("Sprintf(%q, &flagprinter) => %q, want %q", tt.in, s, tt.out)}

}}

Page 8: Go Friday 傑作選

サブテスト

■ 子テストを実行するしくみ● Go1.7から導入された

func TestAB(t *testing.T) {t.Run("A", func(t *testing.T) { t.Error("error") })t.Run("B", func(t *testing.T) { t.Error("error") })

}

go test -v sample -run TestAB/A=== RUN TestAB=== RUN TestAB/A--- FAIL: TestAB (0.00s) --- FAIL: TestAB/A (0.00s) sample_test.go:10: errorFAILexit status 1FAIL sample 0.007s

サブテストを指定して実行

Page 9: Go Friday 傑作選

サブテストとテーブル駆動テスト

func TestIsOdd(t *testing.T) {cases := []*struct {name string; input int; expected bool}{

{name: "+odd", input: 5, expected: true},{name: "+even", input: 6, expected: false},{name: "-odd", input: -5, expected: true},{name: "-even", input: -6, expected: false},{name: "zero", input: 0, expected: false},

}

for _, c := range cases {t.Run(c.name, func(t *testing.T) {

if actual := IsOdd(c.input); c.expected != actual {t.Errorf(

"want IsOdd(%d) = %v, got %v",c.input, c.expected, actual)}})

}}

Page 10: Go Friday 傑作選

テストヘルパー

■ テスト用のヘルパー関数● ヘルパー関数はエラーを返さない● *testing.Tを受け取ってテストを落とす● Go 1.9からはT.Helperを使って情報を補足する

○ https://tip.golang.org/pkg/testing/#T.Helper

func testTempFile(t *testing.T) string { t.Helper() tf := ioutil.TempFile("", "test") if err != nil { t.Fatal("err %s", err) } tf.Close() return tf.Name()}

Page 11: Go Friday 傑作選

APIクライアントを作る時のテストどうしよう

■ ハンドラのテストはnet/http/httptestを使う

● ハンドラのテストのための機能など提供

● ResponseRecorder○ ResponseWriterを実装している

● NewRequestメソッド(1.7以上)○ 簡単にテスト用のリクエストが作れる

Page 12: Go Friday 傑作選

ハンドラのテストの例func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, net/http!")}

func TestSample(t *testing.T) {w := httptest.NewRecorder()r := httptest.NewRequest("GET", "/", nil)handler(w, r)rw := w.Result()defer rw.Body.Close()if rw.StatusCode != http.StatusOK { t.Fatal("unexpected status code") }b, err := ioutil.ReadAll(rw.Body)if err != nil { t.Fatal("unexpected error") }const expected = "Hello, net/http!"if s := string(b); s != expected { t.Fatalf("unexpected response: %s", s) }

}

テスト対象

テストコード

Page 13: Go Friday 傑作選

HTTPハンドラのモック

■ net/http/httptest.Serverを使う● https://play.golang.org/p/KADrbDUEBp

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, r.RequestURI)

}))defer ts.Close()

tsURL, err := url.Parse(ts.URL)if err != nil { log.Fatal(err) }

client := ts.Client()client.Transport = &http.Transport{ Proxy: http.ProxyURL(tsURL) }

res, err := client.Get("http://example.com")if err != nil { log.Fatal(err) }

Page 14: Go Friday 傑作選

HTTPSだと動かない!

Page 15: Go Friday 傑作選

HTTPSの場合

■ RoundTriper を実装し、URLを書き換える● https://play.golang.org/p/WSSal0bVMs

type RewriteTransport struct {Transport http.RoundTripperURL *url.URL

}

func (t RewriteTransport) RoundTrip(req *http.Request) (*http.Response, error) {req.URL.Scheme = t.URL.Schemereq.URL.Host = t.URL.Hostreq.URL.Path = path.Join(t.URL.Path, req.URL.Path)rt := t.Transportif rt == nil { rt = http.DefaultTransport }return rt.RoundTrip(req)

}リクエストを書き換えるのはあまりよくないが ...

Page 16: Go Friday 傑作選

HTTPSの場合

■ RoundTriper を実装し、URLを書き換える● https://play.golang.org/p/WSSal0bVMs

s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "hello %s\n", path.Base(r.URL.Path))

}))u, err := url.Parse(s.URL)

if err != nil { log.Fatalln("Error:", err) }cli := s.Client()cli.Transport = RewriteTransport{URL: u, Transport: cli.Transport}resp, err := cli.Get("https://example.com")

if err != nil { log.Fatalln("Error:", err) }io.Copy(os.Stdout, resp.Body)resp.Body.Close()

Page 17: Go Friday 傑作選

Thank you!

twitter: @tenntenn Qiita: tenntennconnpass: tenntenn

17