なんか最近みんな書いてる golang 。OSS へのコントリビュートチャンスも増えてきました。自分でバリバリ書くには時間も自信もない。でも、バグ修正くらいならやってみたい。それくらいの目的のために最低限必要な知識を書いてみました。
この記事では、自分ではバリバリ書けないけれど、golang の OSS プロジェクトには貢献したいという人向けに、出来るだけシュッと学べるように重要なポイントのみ絞って紹介していきます。
初心者向けの優しい解説記事という訳ではないので、自分で調べるためのキッカケとしての読み方を想定しています。また、自分自身が想定読者のため、間違ったことを書いている可能性もあります。という逃げも書いておきます。
開発環境
基本的に Mac OSX + HomeBrew + Atom を前提とします。といいつつ、golang も Atom もすべてのプラットフォームで動くはずなので、HomeBrew の部分だけ、各プラットフォームに置き換えてもらえると良さそうです。
golang
$ bew install go $ echo "export GOPATH=~/.go" >> ~/.zshrc
golang では言語自体にパッケージマネージャの機能があり、go get URL
でパッケージを取得できます。その際に GOPATH
に指定したディレクトリ以下に配置されます。GOPATH
は特にしばりはなく、好きな場所を指定するといいようです。Ruby 畑出身の僕は rbenv っぽい感覚で ~/.go
を指定していますが、ghq を使い始めてからは普通のリポジトリもこの下に入れているので、若干失敗した感は否めません。
Atom
コードを書いている時間のほとんどを Vim と一緒に過ごしてきた僕ですが、昨年のβ公開時より Atom に移行しました。まだ、設定ファイルや、簡単な修正の場合は Vim を使うこともありますが、基本的には Atom に満足していて、メインで使っています。
golang の言語サポート自体はデフォルトパッケージとして付属しているので、go-plus と autocomplete-plus だけインストールします。
$ apm install autocomplete-plus go-plus
もちろん Settings からワンクリックでインストールすることも出来ます。
autocomplete-plus
https://atom.io/packages/autocomplete-plus
Atom には autocomplete という補完用のパッケージが付属していますが、このパッケージでは補完のためにキー入力が必要です。autocomplete-plus
をインストールすると、例えば .
を入力したときなど、補完が可能な時は自動的に候補を表示してくれるようになります。
autocomplete-snippets を入れるとスニペットも候補として表示してくれるようになるので、こちらもおすすめです。
go-plus
https://atom.io/packages/go-plus
言語サポートではシンタックスハイライトやスニペットの提供をしてくれるのだけれど、この go-plu ではさらに gocode
を使った補完や gofmt
での整形、 go-imports
での import
の自動挿入などなど、golang の便利なツール群と Atom の連携を実現してくれます。正直にわかにとってはこれがないとコードが書けないくらい便利です。
入門
とりあえず A Tour of Go をひと通りやれば良さそうです。 基本的なことはほぼ全て網羅しているので、これをやるだけで golang 力が上がります。
あとは必要なところの ドキュメント をそのつど読めばなんとかなります。 Mac でドキュメント読むには Dash for Mac がおすすめ。
サンプルコードはすべてそのままコピペで動くようにしています。 そのため本質と関係のないコードも書いてありますが了承ください。
実行方法
例えば sample.go
というファイルにコードを記述したとすると、以下の方法でコンパイルして実行できます。
$ go build sample.go ./sample
実は go run
を使うと、コンパイルと実行を同時にやってくれるので、ぱっと実行するにはこちらの方がおすすめです。
$ go run sample.go
サードパーティのパッケージを取得するには go get
を使います。
例えば、termbox-go は以下のようにして取得します。
$ go get github.com/nsf/termbox-go
変数
golang の変数は var 変数名 型
のように宣言することできます。
また、初期化子を指定することで宣言と同時に値を代入できます。
複数の変数を定義する場合で、型が同じ場合は最後に型を書けば全ての変数がその型であることを指定できます。
そして :=
を使うことにより、型推論をさせることも出来ます。型を明示しないといけない場合以外は、この方法で定義しておけば間違いなさそうです。
余談ですが :=
をなんと読むかは諸説あります。(see also: := 演算子をなんと呼ぶのか問題)
golang では定義した変数は必ず使わなければいけず、コンパイル時にエラーになります。_
という名前の変数だけは特別にこのルールが適用されません。
package main import "fmt" func main() { var name string name = "Bob" fmt.Println(name) //=> Bob var name2 string = "Alice" fmt.Println(name2) //=> Alice var num, num2 int = 1, 2 fmt.Println(num, num2) //=> 1 2 isChecked := true fmt.Printf("%v", isChecked) //=> true }
型
基本型がいくつかあります。
- bool
- string // byte のスライス
- int int8 int16 int32 int64
- uint uint8 uint16 uint32 uint64 uintptr
- byte // uint8 の別名
- rune // int32 の別名、Unicode のコードポイントを表す
- float32 float64
- complex64 complex128
int8
は 8bit の符号付き整数、unit8
は 8bit の符号なし整数で、後はこの法則に従います。
uint
は環境によって 32bit もしくは 64bit の符号なし整数となります。int
は uint
と同じサイズの符号付き整数です。
しかし、uint
が 32bit であった場合も、unit32
とは別の型になるので、明示的な型変換が必要です。
型変換は 型名(値)
と書きます。
package main import "fmt" func main() { var a uint32 var b uint = 5 // a = b error! a = uint32(b) fmt.Println(a) //=> 5 }
また、golang では文字列を表現する方法が string
と []rune
の2種類あり、前者は文字を byte
のスライスとして扱い、後者は rune
のスライスとして扱います。
byte
は 8bit しか表さないのに対して、rune
は Unicode 文字そのものを表します。また、string の range()
を取ると rune
が返るため、「文字」毎にループを回すということが実現出来ています。
スライス
や range
については後述します。
package main import "fmt" func main() { s := "日本語" r := []rune(hoge) fmt.Printf("%#U\n", s[0]) //=> U+00E6 'æ' fmt.Printf("%#U\n", r[0]) //=> U+65E5 '日' }
配列 と マップ
golang では配列を [サイズ]型
と書きます。また {}
を使って初期化することも出来ます。初期化されていない要素は勝手にデフォルトの初期値が入っています。
package main import "fmt" func main() { var array1 [5]int fmt.Println(array1) //=> [0 0 0 0 0] array1[2] = 5 fmt.Println(array1) //=> [0 0 5 0 0] array2 := [3]string{"one", "two", "three"} fmt.Println(array2) // => [one two three] fmt.Println(array2[1]) //=> two array3 := [...]string{"面倒だったら", "サイズを省略", "することも出来る"} fmt.Println(array3) // [面倒だったら サイズを省略 することも出来る] fmt.Println(len(array3)) //=> 3 }
いわゆる連想配列は map を使って定義します。
package main import "fmt" func main() { var map1 map[string]int = map[string]int{} map1["one"] = 1 fmt.Println(map1) //=> map[one:1] fmt.Println(map1["one"]) //=> 1 map2 := map[int]bool{1: true, 2: false} fmt.Println(map2[2]) //=> false }
スライス
golang の配列は直感に反してポインターではなくて、実際に配列自体を表しています。でも、毎回値をコピーするのは非効率。そこでスライスが存在します。 スライスは、配列へのポインタ、配列のサイズ、配列のキャパシティ を持っており、スライス間では配列のデータ自体は共有されます。 (see also: Go言語のスライスを理解しよう)
スライスを作る方法は3つあります。
1つめは配列から作る方法で s := array[:]
と書きます。
2つめはリテラルで書く方法で s := []int{1, 2, 3}
と書きます。
3つめは make
を使った方法で、make([]string, 3, 5)
のようにサイズとキャパシティを指定できます。
キャパシティを省略した場合はサイズと同じ長さになります。
package main import "fmt" func main() { a := [...]int{1, 2, 3, 4, 5} fmt.Printf("%#v\n", a) //=> [5]int{1, 2, 3, 4, 5} s1 := a[:] fmt.Printf("%#v\n", s1) //=> []int{1, 2, 3, 4, 5} s2 := []string{"cat", "dog"} fmt.Printf("%#v\n", s2) //=> []string{"cat", "dog"} s3 := make([]bool, 2, 5) fmt.Printf("%#v\n", s3) //=> []bool{false, false} fmt.Println(len(s3)) //=> 2 fmt.Println(cap(s3)) //=> 5 s3 = s3[:cap(s3)] fmt.Println(len(s3)) //=> 5 s4 := make([]uint, 3) fmt.Println(cap(s4)) //=> 3 }
また、スライスを再スライスすることも出来ます。再スライスによってスライスの一部だけを取り出すことが出来ます。 再スライスした場合でも参照しているメモリ領域は同じため、片方を変更するとどちらにも影響があります。
再スライスは スライス[開始位置:終了位置]
と書きます。片方を省略した場合は、それぞれの端までを指定したことになります。
package main import "fmt" func main() { s1 := []int{1, 2, 3, 4, 5} fmt.Printf("%#v len=%d, cap=%d\n", s1, len(s1), cap(s1)) //=> []int{1, 2, 3, 4, 5} len=5, cap=5 s2 := s1[1:3] fmt.Printf("%#v len=%d, cap=%d\n", s2, len(s2), cap(s2)) //=> []int{2, 3} len=2, cap=4 fmt.Printf("%#v\n", s1[:3]) //=> []int{1, 2, 3} fmt.Printf("%#v\n", s1[2:]) //=> []int{3, 4, 5} s3 := s2[:1] s3[0] = 10 fmt.Printf("%#v\n", s2) //=> []int{10, 3} }
新しいメモリ領域にスライスの一部をコピーしたい場合は copy
を使います。
package main import "fmt" func main() { s1 := []int{1, 2, 3, 4, 5} s2 := make([]int, 2) copy(s2, s1[1:3]) //=> []int{2, 3} fmt.Printf("%#v\n", s2) s2[0] = 5 fmt.Printf("s1: %#v\n", s1) //=> s1: []int{1, 2, 3, 4, 5} fmt.Printf("s2: %#v\n", s2) //=> s2: []int{5, 3} }
定数
定数は const
を使って定義します。定数は 文字、文字列、真偽値、整数 のみを扱えます。
ただし型が指定出来ないので定数が使われるコンテキストに合わせて型が決まります。桁あぶれに注意。
package main import "fmt" const ( One = 1 Two = 2 Three = 3 ) func main() { fmt.Println(One) //=> 1 fmt.Println(Two) //=> 2 fmt.Println(Three) //=> 3 }
値が連番になっている場合は、以下のような書き方も出来ます。
package main import "fmt" const ( One = iota + 1 Two Three ) func main() { fmt.Println(One) //=> 1 fmt.Println(Two) //=> 2 fmt.Println(Three) //=> 3 }
制御文
if
、for
、switch` などおなじみのものが使えます。括弧は省略します。
if
if
はお馴染みの書き方ですが、条件式の中で変数を定義出来るのが特徴です。条件式で定義した変数は if
のスコープの中だけで有効です。
package main import "fmt" func main() { a := 5 if a == 5 { fmt.Println("a is 5") } else { fmt.Println("a is not 5") } //=> a is 5 if twice := a * 2; twice == 5 { fmt.Println("a * 2 = 5") } else if twice == 10 { fmt.Println("a * 2 = 10") } else { fmt.Println("otherwise") } //=> a * 2 == 10 // fmt.Println(twice) error! }
for
for
にはいくつかの書き方があります。
C 言語のような if 初期化; 条件; 制御 {}
のような書き方も出来ますし、for 条件 {}
のように while
の代わりに使うことも出来ます。また、for {}
と書けば無限ループになります。
また、for i, n := range(array) {}
のように range()
と合わせるとイテレータのようなことも出来ます。i
には 0 オリジンのが連番、n
には値が入ります。それぞれ _
という名前にすることで省略できます。
package main import "fmt" func main() { for i := 0; i < 10; i++ { fmt.Println(i) } a := 10 for a >= 0 { fmt.Println(a) a-- } b := 0 for { if b > 10 { break } b++ } array := [...]int{1, 2, 3, 4, 5} for i, n := range array { fmt.Printf("%d: %d\n", i, n) } s := "日本語" for _, c := range s { fmt.Printf("%#U\n", c) } }
switch
switch
もお馴染みの書き方ですが、複数の条件を書ける、標準ではフォールスルーしない、式を書けるなどの特徴があります。
package main import "fmt" func main() { a := 2 switch a { case 1: fmt.Println("a is 1") case 2: fmt.Println("a is 2") default: fmt.Println("otherwise") } switch a { case 1, 2, 3, 4: fmt.Println("a is 1 or 2 or 3 or 4") default: fmt.Println("otherwise") } switch a { case 1: fallthrough // フォールスルーさせる case 2: fmt.Println("a is 1 or 2") } switch { case a == 1: fmt.Println("a is 1") case a == 2: fmt.Println("a is 2") } }
関数
関数は func 名前(引数) 戻り値 {}
という書き方をします。戻り値を指定している場合は return
が必須です。
複数の値を返すことも出来ます。
package main import ( "fmt" "math" ) func add(a, b int) int { return a + b } func print(num int) { fmt.Println(num) } func pow(i, n int) (int, error) { if n > 10 { return 0, fmt.Errorf("n は 10 以下で指定してください") } return int(math.Pow(float64(i), float64(n))), nil } func main() { sum := add(1, 2) print(sum) p, err := pow(10, 100) if err != nil { fmt.Println(err) } else { fmt.Printf("result: %d", p) } }
名前付きの戻り値というものもあって、以下のように sum
がすでに定義された状態として扱え、return
の引数も省略できます。
package main import "fmt" func add(a, b int) (sum int) { sum = a + b return } func main() { sum := add(1, 2) fmt.Println(sum) }
golang の関数はファーストクラスファンクションになっているので、無名関数を作って変数に代入することもできます。また、レキシカルスコープであるのでクロージャを作ることも出来ます。
package main import "fmt" func mkCounter(init int) func() int { count := init return func() int { count++ return count } } func main() { add := func(a, b int) int { return a + b } fmt.Println(add(1, 2)) //=> 3 counter := mkCounter(5) fmt.Println(counter()) //=> 6 fmt.Println(counter()) //=> 7 fmt.Println(counter()) //=> 8 }
エラー処理
複数戻り値の関数の例を抜粋して再掲します。
package main import ( "fmt" "math" ) func pow(i, n int) (int, error) { if n > 10 { return 0, fmt.Errorf("n は 10 以下で指定してください") } return int(math.Pow(float64(i), float64(n))), nil } func main() { p, err := pow(10, 100) if err != nil { fmt.Println(err) } else { fmt.Printf("result: %d", p) } }
この例では error
型の戻り値を返しています。golang では Java などでいう 例外
が存在しないので、自分でエラーを返す必要があります。
定義した変数は必ず使わなければいけないルールがあるため、この方法でエラーを返すと必ずエラー処理をする必要があり、より安全なコードになることが期待出来ます。
標準ライブラリの関数も同様の方法でエラーを返します。
構造体
golang にはクラスの概念がありません。代わりに構造体を使ってオブジェクトのようなことを実現します。
構造体は type 名前 struct {}
という書き方で定義でき、いくつかのフィールドを持つことができます。
A{1, 2}
のように初期化して、各フィールドには a.X
のように .
を使ってアクセスできます。
package main import "fmt" type A struct { X int Y int } func main() { a := A{1, 2} fmt.Printf("X: %d\n", a.X) //=> X: 1 fmt.Printf("Y: %d\n", a.Y) //=> Y: 2 a.X = 5 fmt.Printf("%#v\n", a) //=> main.A{X:5, Y:2} }
メソッドを定義することも出来ます。少し独特なので最初は気持ち悪いかもしれませんが、そのうち慣れます。
基本は func (構造体) メソッド名(引数) 戻り値 {}
のように書くと、指定した構造体のメソッドを定義出来ます。
またここで指定した変数を使って呼び出し元の構造体にアクセスすることが出来ます。
つまり、person.setName("Alice")
では p
に person
が、name
に Alice
が格納されます。
package main import "fmt" type Person struct { name string age int } func (p Person) greet() { fmt.Printf("Hi, my name is %s, %d years old.\n", p.name, p.age) } func (p *Person) setName(name string) { p.name = name } func (p *Person) setAge(age int) { p.age = age } func main() { person := &Person{"Bob", 18} person.greet() person.setName("Alice") person.setAge(28) person.greet() }
(p Person)
と (p *Person)
という2種類の書き方がされているのに気付いたかたもいらっしゃるかと思います。
golang にもポインタが存在していて、C言語などと同じように &
でアドレスを表し、*
で値を取り出せます。
構造体の場合は、構造体のポインタから直接フィールドやメソッドを呼び出せるようになっているので、簡単にポインタを扱えます。
この例では person := &Person{"Bob", 18}
としているので、person
には Person
構造体のインスタンスへのポインタが格納されていることになります。
では、なぜメソッドの定義でポインタとそうでないものがあるのでしょうか。golang では基本的には値渡しがなされます。
つまり func (p Person) ...
という定義では、p
は呼び出し元の構造体をコピーしたものが渡されてしまいます。
よって、いくらメソッドの中でフィールドの値を更新したところで、呼び出し元の構造体のフィールドは更新されないということになってしまいます。
そこで、func (p *Person) ...
という指定をすることによって、ポインタで扱うことにすれば、参照渡しとなるので、呼び出し元のフィールドの更新を実現出来るのです。
また、以下のように構造体の定義の先頭に別の構造体のインスタンスを配置することで、継承のようなことも実現できます。
package main import "fmt" type Programmer struct { language string } func (p Programmer) coding() { fmt.Printf("%s を書いています\n", p.language) } func newProgramer(langeage string) *Programmer { return &Programmer{langeage} } type Person struct { *Programmer name string age int } func (p Person) greet() { fmt.Printf("Hi, I'm %s, %d years old.\n", p.name, p.age) } func newPerson(name string, age int, language string) *Person { return &Person{ Programmer: newProgramer(language), name: name, age: age} } func main() { bob := newPerson("bob", 18, "Java") bob.greet() //=> Hi, I'm bob, 18 years old. bob.coding() //=> Java を書いています alice := newPerson("alice", 28, "Scala") alice.greet() //=> Hi, I'm alice, 28 yeard old. alice.coding() //=> Scala を書いています }
インターフェース
ダックタイプ とは、例えば「がーがーと鳴くもの」を「アヒル」とみなすという風に、あるメソッドを持つオブジェクトを型によらず同じものであると扱う手法です。 Ruby などのように型が存在しない場合は簡単に実現出来ますが、型のある golang でダックタイプのようなものを実現したいときにはどうすればいいのでしょうか。
golang にはインターフェースという仕組みがあり、これを用いるとダックタイプを実現出来ます。
package main import "fmt" type Person interface { greet() } type Man struct { name string age int } func (p Man) greet() { fmt.Printf("Hi, my name is %s, %d years old.\n", p.name, p.age) } type Woman struct { name string } func (p Woman) doGreet() { fmt.Printf("Hi, my name is %s, 18 years old.\n", p.name) } func greet(person Person) { person.greet() } func main() { bob := &Man{"Bob", 18} doGreet(bob) alice := &Woman{"Alice"} doGreet(alice) }
ここでは Person
というインターフェースを定義しており、Man
と Woman
がこのインターフェースを実装しています。
doGreet
関数は Person
インターフェースを実装している構造体を引数に取っており、Man
と Woman
どちらのインスタンスも受け付けます。
パッケージ
golang でもよくあるパッケージでのネームスペースの管理が出来ます。
これまで package main
と書いていたのは main
パッケージの中にコードを書いていたことになります。
新しく another
パッケージを導入するときには以下のようなディレクトリ構成にします。
$ tree . . ├── another │ └── another.go └── main.go
another
パッケージは以下のように書いてみます。
package another func NewA(value int) *A { return &A{value} } type A struct { value int } func (a A) Value() int { return a.value } func newb(value int) *b { return &b{value} } type b struct { value int } func (b b) Value() int { return b.value }
ここではあえて camelCase
と CamelCase
を混ぜています。
実は golang ではこの違いには意味があります。
1文字目が大文字、つまり CamelCase
にした場合は、パッケージの外部に公開され、camelCase
の場合はパッケージの中に秘匿されるというルールがあり、この場合は構造体 A
や関数 NewA
は外部に公開され、構造体 b
や関数 newB
は外部には公開されません。
このパッケージを使うには以下のように相対パスでパッケージを指定します。
package main import ( "fmt" "./another" ) func main() { a := another.NewA(5) fmt.Println(a.Value()) // b := newB(10) error! }
ただし、注意点があって、GOPATH
以下に配置されているプロジェクトの場合は相対パスでの指定は出来ません。GOPATH
を基準としてパスで指定する必要があります。
例えば GitHub でホスティングしているプロジェクトの場合は以下のようになるでしょう。
package main import ( "fmt" "github.com/User/Repo/another" ) func main() { a := another.NewA(5) fmt.Println(a.Value()) }
並行処理
この辺りからだんだん説明があやしくなります。
golang は言語機能として並行処理をサポートしています。以下のように goroutine を使えば通常の関数を並行して実行することが出来ます。
例では定義した関数を使用していますが、無名関数を使う事もできます。その場合は go func() { ... }()
のように定義した関数を呼び出してやる必要があります。
package main import ( "fmt" "time" ) func loop(name string) { for i := 0; i < 5; i++ { fmt.Printf("%s: %d\n", name, i) time.Sleep(1) } } func main() { go loop("a") go loop("b") for i := 0; i < 5; i++ { fmt.Printf("main: %d\n", i) time.Sleep(1) } } // main: 0 // a: 0 // b: 0 // main: 1 // a: 1 // b: 1 // main: 2 // a: 2 // b: 2 // main: 3 // a: 3 // b: 3 // main: 4 // a: 4 // b: 4
並行処理とは少し違いますが、defer
を使うと関数の実行を遅延することが出来ます。いつまで遅延するかというと、現在の関数が終了するまで遅延します。
package main import "fmt" func main() { defer func() { fmt.Println("終了しました") }() fmt.Println("処理中です") }
この例だと全く嬉しくないのですが、たとえば関数が終了したときにリソースを開放するなどの処理が簡単に書けるため非常にべんりです。
万能のように見える goroutine ですが、平行して実行されるということは、Thread のようにリソースの扱いに注意が必要です。
複雑なことをしようとすると sync
などのパッケージを使って同期を取る必要も出てきます。(see also: Big Sky :: golang の sync パッケージの使い方)
チャネル
並行で走っている goroutine 間でデータのやりとりをするにはどうすればいいのでしょうか。
もちろんこの問題の解決策も言語機能として用意されています。それにはチャネルを使います。
チャネルは make
を使って make(chan 型)
のように作成します。指定した型をやり取りすることが出来るチャネルが生成されます。
package main import "fmt" func add(a, b int, ch chan int) { ch <- a + b } func main() { ch := make(chan int) go add(1, 5, ch) sum := <-ch fmt.Println(sum) //=> 6 close(ch) _, ok := <-ch fmt.Println(ok) //=> false }
add
関数をチャネルを使って書き換えてみました。ch <- a + b
という書き方でチェネルに値を送信して、sum := <- ch
でチャネルから値を受け取っています。
<- ch
と書いた時点で、チャネルから値が送られてくるか、close(ch)
のようにしてチャネルが閉じられるまで待つことが出来ます。
また、2つ目の戻り値を見るとチャネルが閉じているかどうかを確認することが出来ます。
range
を使えばチェネルから連続して送られてくる値を受け取ることが出来ます。ただし、適切にチャネルをクローズしてやらないとデッドロックを起こしてプログラムがクラッシュしてしまいます。
package main import "fmt" func main() { ch := make(chan int) go func() { for i := 0; i < 5; i++ { ch <- i } close(ch) }() for i := range ch { fmt.Println(i) } } //=> 0 //=> 1 //=> 2 //=> 3 //=> 4
select
を使うと複数のチャネルを待ち受けることが出来ます。今回は外側のループを抜けるためにラベルを指定しています。
package main import "fmt" type Hub struct { outCh chan int quitCh chan bool } func fibonacci(count int, hub *Hub) { a, b := 1, 1 for i := 0; i < count; i++ { hub.outCh <- a a, b = b, a+b } hub.quitCh <- true } func main() { hub := &Hub{ outCh: make(chan int), quitCh: make(chan bool), } go fibonacci(10, hub) LOOP: for { select { case n := <-hub.outCh: fmt.Println(n) case <-hub.quitCh: break LOOP } } }
テスト
golang のテストは標準添付の testing
パッケージを使います。あまりのシンプルさに他の言語からきた場合は各言語のテスティングフレームワークとの落差に驚かれるかもしれません。
例えば先ほどの another パッケージのテストを書く場合は以下のようにします。ファイル名を 任意の名前_test.go
とする必要があります。
package main import ( "testing" "./another" ) func TestAnotherA(t *testing.T) { a := another.NewA(5) if a.Value() != 5 { t.Errorf("a.Value() should be 5") } }
実行は go test
を使います。
$ go test another_test.go ok command-line-arguments 0.003s
無事通ったことを確認できました。引数を省略したときはカレントディレクトリの xxx_test.go
をまとめて実行してくれます。
TextXXX
という名前ので *testing.T
型の引数を取るメソッドを実行してくれます。よくある assert
などは存在せず、if
などでチェックして、問題がある場合は t.Errorf()
を呼ぶとテストに失敗するという仕組みです。
テストについてはこの記事がとても参考になりました。
まとめ
- Atom + go-plus が快適
- A Tour of Go をやるといい
- 補完が効きまくるし、エラーはコンパイラが拾ってくれるので、読めさえすればなんとかなる