Golang Cafe #1 まとめ
2013/10/27に開催された「Golang Cafe #1」についてのまとめです。
Go環境の準備については、Golang Cafe #0(準備編)にまとめてあります。
今回のお題
How to Write Go Codeをやる。
(WorkspaceとTestの辺りまで)
WorkspaceとPackage
WorkspaceとPackageの考え方
あるディレクトリをルートとして(今回はc:\gocafe)そのディレクトリ以下をワークスペースとします。
ワークスペース内には以下の3つのディレクトリを配置します。
- src ソースコードを配置
- pkg ビルドされたパッケージを配置
- bin ビルドされた実行コマンドを配置
最低限必要なのはsrcディレクトリだけで、pkgおよびbinディレクトリは必要に応じて勝手に作成されます。
go toolを使いやすくするために、このワークスペースのパスを環境変数に追加し、binディレクトリにパスを通しておきます。
GOPATH=c:\gocafe PATH=%PATH%;%GOPATH%\bin; ※実際は環境変数に追加しておきます。
srcディレクトリ配下は、1ディレクトリ1パッケージ(プロジェクト)という形になります。
各ルートパッケージ名は、リモートリポジトリを使用する場合は、
github.com/username/パッケージ名/
そうでない場合は、
jp.co.hoge/パッケージ名/
の形がお薦めらしい。
パッケージ直下にはソースコードを配置します。
パッケージ名とソースコードのファイル名は同じでなくてもよく、ソースコードのpackage宣言とパッケージ名が同じであれば良い。
例えば以下のような形でも良い。
GOPATH/github.com/username/hello/sample.go
sample.go
package hello import "fmt" func main() { fmt.Printf("Hello, world\n") }
但しメインパッケージは例外で、パッケージ宣言はmainで、パッケージ名.exeが作成されます。
また、パッケージには階層を持たせることができます。
Java言語のようにパッケージ階層とディレクトリ階層は一致させておく必要がありますが、ソースコードのpackage宣言は自分が所属するパッケージ名だけになります。
GOPATH/github.com/username/mylib1/mylib2/mylib3/sample.go
package mylib3 import "fmt" func myfunc() { fmt.Printf("myfunc\n") }
簡単なプログラム
helloディレクトリを作成しsample.goを作成します。
GOPATH/src/github.com/username/hello/sample.go
package main import "fmt" func main() { fmt.Printf("Hello, world!\n") }
$ go install github.com/username/hello または $cd GOPATH/src/github.com/username/hello $go install
を実行すると、GOPATH/binにhello.exeが作成されます。
(ディレクトリの先頭のsrcは不要です。自動的にsrcディレクトリ以下を調べるようです。パス指定する場合は./src/を付けます。)
そしてGOPATH/binには既にパスを通してあるので、
$ hello Hello, world!
と結果が得られます。
簡単なライブラリ
mylib1ディレクトリを作成しsample.goを作成します。
GOPATH/src/github.com/username/mylib1/sample.go
package mylib1 func Div(x int, y int) float64 { // go言語でのキャストはJavaやC#とは異なり型ではなく変数の方を括弧で囲みます。 return float64(x) / float64(y) }
そしてhello/sample.goを修正します。
package main import ( "fmt" "github.com/username/mylib1" ) func main() { fmt.Printf("Hello, world! Div(%v, %v)=%v", 5, 2, mylib1.Div(5, 2)) }
$ go build github.com/username/mylib1
とするとエラー等がある場合はここで表示されます。しかし出力は何もありません。
git installまたはこのパッケージを使用するパッケージでgo installを実行するとpkgディレクトリが作成されmylib1.aが出力されます。
$ hello Hello, world! Div(5, 2)=2.5
と結果が得られれば成功です!
因みにここまで終わった段階でワークスペース以下のディレクトリ構成は以下のようになっています。
bin/ hello.exe pkg/ windows_amd64/github.com/username/mylib1.a src/ github.com/username/hello/sample.go github.com/username/mylib1/sample.go
Testing
Go言語にはデフォルトでtestingパッケージが用意されており、今回はこちらを利用します。
testingパッケージを利用する場合のお約束事
- テストするパッケージと同じパッケージに配置する
- ファイル名は_test.goで終わるようにする
- TestXxxxの関数名にする、Xxxxはテスト対象の関数名
- 引数はt *testing.T
簡単なテスト
ここでは上記で説明した「簡単なライブラリ」に対するテストを考えます。
mylib1ディレクトリにsample_test.goを作成します。
GOPATH/src/github.com/username/mylib1/sample_test.go
package mylib1 import "testing" func TestDiv(t *testing.T) { // if文の中に代入式を書くことが出来ます。 // スコープはifブロック中でelseの中でも参照できます。 if x := Div(5, 2); x != 2.5 { t.Errorf("Div(%v, %v) = %v, want %v", 5, 2, x, 2.5) } }
$ go test github.com/username/mylib1 または $cd GOPATH/src/github.com/username/mylib1 $go test
を実行しテストが通ると、
ok github.com/username/mylib1 0.458s
のように表示され、失敗すると、
--- FAIL: TestDiv (0.00 seconds) sample_test.go:9: Div(5, 2) = 3.5, want 2.5 FAIL FAIL github.com/taknb2nch/mylib1 0.429s
のように表示されます。
ここで非常に分かりにくいのが、複数のテストが存在して一部のテストが失敗した場合、失敗したテストの出力しかされません。成功したテストの"成功しました"という出力はありません。
そういう場合は-vオプションを指定することですべて出力されるようです。(オプションの詳細な説明はこちら)
$ go test -v === RUN TestDiv --- FAIL: TestDiv (0.00 seconds) sample_test.go:12: Div(5, 2) = 3.5, want 2.5 === RUN TestAdd --- PASS: TestAdd (0.00 seconds)
また複数のパッケージをテストする場合は、
$ go test github.com/username/mylib1 github.com/username/mylib1/mylib2
のように複数のパッケージをスペースで区切って指定します。
go test -short
とすればプログラム中でt.Skipped()の値を参照して処理を振り分けられるようです。
テスト出力には他にもFatal、LogあるいはSkipなどがありますが、JUnitやNUnit等と比べると正直使いやすいとはいえません。
(ちなみにLog、Skipはgo test -vとする必要があるようです)
Benchmark
Go言語ではベンチマークをとることもできるようです。
テストの場合とお約束が若干違って
- BenchmarkXxxxの関数名にする、Xxxxはベンチマーク対象の関数名
- 引数はb *testing.B
となります。
簡単なベンチマーク
ここでは上記で説明した「簡単なライブラリ」に対するベンチマークを考えます。
mylib1ディレクトリのsample_test.goに追加します。
func BenchmarkDiv(b *testing.B) { for i := 0; i < b.N; i++ { Div(5, 2) } }
$ go test -bench . github.com/username/mylib1 または $cd GOPATH/src/github.com/username/mylib1 $go test -bench .
を実行すると、
PASS BenchmarkDiv 1000000000 2.82 ns/op ok github.com/username/mylib1 3.549s
と表示されます。このベンチマークでは1000000000回ループして2.82ns/1回ということになります。
注意すべき点は、テストコードが存在している場合、すべてのテストがOKでないとベンチマークは実行されません。
Examples
もう一つExamplesというのがあるようです。いまいち用途を理解できていません。
こちらのお約束も少し違って、
- ExampleXxxxの関数名にする、Xxxxは適当な名前
- 引数はなし
となります。
簡単なExamples
mylib1ディレクトリのsample_test.goに追加します。
func ExampleHello1() { fmt.Println("hello") // Output: hello } func ExampleHello2() { fmt.Println("hello, and") fmt.Println("goodbye") // Output: // hello, and // goodbye }
$ go test -v github.com/username/mylib1 または $cd GOPATH/src/github.com/username/mylib1 $go test -v
を実行すると、
=== RUN: ExampleHello1 --- PASS: ExampleHello1 (0) === RUN: ExampleHello2 --- PASS: ExampleHello2 (0)
と表示されます。要は関数内で標準出力に出力された内容がOutput:以降の内容と等しいかどうか判定しているようです。
結局はよくわからないよね、で終わってしまった。
ドキュメントを読み込んでみないといけませんね。