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
自己紹介
上田拓也
@tenntenn
2
所属
コミュニティ活動
&
Go ビギナー
Go Conference
Go Friday
■ GoやGCPに関するソウゾウの社内勉強会
● 社外で得た知見の共有
○ 海外カンファレンスについての共有
○ コミュニティで得た知識の共有
● 社内の各プロダクト開発で得た知見の共有
○ 設計の方針や実装方法の共有
○ 共通ライブラリの共有
● 社外へのアウトプットの練習
○ 発表練習
○ イベント登壇のネタ出し
○ 社内ライブラリのOSS化の相談
参考:https://codeiq.jp/magazine/2017/04/50250/
switchを使おう
const (
StatusA = 0
StatusB = 1
StatusC = 0 // miss
)
switch s {
case StatusA:
fmt.Println("A")
case StatusB:
fmt.Println("B")
case StatusC: // error
fmt.Println("C")
}
ついに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/
@mitchellh のテストの話よい
■ Advanced Testing with Go
● https://speakerdeck.com/mitchellh/advanced-testing-with-go
● テーブルドリブンテスト
● サブテスト
● ヘルパー関数
テーブル駆動テスト
■ テスト対象のデータを羅列してテストする
var flagtests = []struct {
in string
out string
}{
{"%a", "[%a]"}, {"%-a", "[%-a]"}, {"%+a", "[%+a]"},
{"%#a", "[%#a]"}, {"% a", "[% a]"},
}
func TestFlagParser(t *testing.T) {
var flagprinter flagPrinter
for _, 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)
}
}
}
サブテスト
■ 子テストを実行するしくみ
● 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: error
FAIL
exit status 1
FAIL sample 0.007s
サブテストを指定して実行
サブテストとテーブル駆動テスト
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)}})
}
}
テストヘルパー
■ テスト用のヘルパー関数
● ヘルパー関数はエラーを返さない
● *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()
}
APIクライアントを作る時のテストどうしよう
■ ハンドラのテストはnet/http/httptestを使う
● ハンドラのテストのための機能など提供
● ResponseRecorder
○ ResponseWriterを実装している
● NewRequestメソッド(1.7以上)
○ 簡単にテスト用のリクエストが作れる
ハンドラのテストの例
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) }
}
テスト対象
テストコード
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) }
HTTPSだと動かない!
HTTPSの場合
■ RoundTriper を実装し、URLを書き換える
● https://play.golang.org/p/WSSal0bVMs
type RewriteTransport struct {
Transport http.RoundTripper
URL *url.URL
}
func (t RewriteTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.URL.Scheme = t.URL.Scheme
req.URL.Host = t.URL.Host
req.URL.Path = path.Join(t.URL.Path, req.URL.Path)
rt := t.Transport
if rt == nil { rt = http.DefaultTransport }
return rt.RoundTrip(req)
}
リクエストを書き換えるのはあまりよくないが ...
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 %sn", 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()
Thank you!
twitter: @tenntenn
Qiita: tenntenn
connpass: tenntenn
17

Go Friday 傑作選