😎

Gopher塾でGoのテストに入門した【パート1 入門編】

2022/11/11に公開

はじめに

tenntennさん 主催の Gopher塾 #1 - Testing - でGoのテストに入門してきました.Gopher塾とは

Gopher塾は、tenntennによるGoに関する有料の講義です。第1回目はテストについて行います。
(中略)
以下の内容を演習を交えながら解説を行う予定です。

  • Goのテスト基礎
    • (略)
  • テストテクニック
    • (略)
  • ベンチマーク
    • (略)
  • Fuzzing
    • (略)

connpassページより引用.段落構成の都合上一部省略しています.詳細はリンク先から確認してください.)
ということで,Gopher道場などの初学者向けの教材よりも実践的な内容について学ぶことができます.Gopher塾,めちゃくちゃよかったのでみなさんぜひ参加してください!!(多分また開催されると思う)ちなみに学生用の無料枠もあります.

今回は参加記事ということでGoのテストについて,特に個人的に面白かった

  • Goのテストの基礎の基礎(これ)
  • テーブル駆動テスト
  • Fuzzing

の3本立てで記事を書かせて頂きました.この記事はパート1ということで「Goのテストの基礎の基礎」編です.パート2, 3も後日公開したいと思います!!

Goのテストの基礎の基礎

簡単なテストの実装

Goには go test が標準で内蔵されており,単体テストであれば簡単に実行することができます.

Goのテストは関数単位で行われ,テストを行う関数の名前は Test から始める必要があります.例えば,引数に与えられた2つのint型整数の和を返す関数 myadd を実装し,さらに myadd をテストする関数 TestMyadd を実装してみます.

main.go
package main

import "testing"

func myadd(a, b int) int {
    return a + b
}

func TestMyadd(t *testing.T) {
    want := myadd(1, 2)
    got := 3 // 1 + 2 = 3 になってほしい
    if want != got {
        t.Errorf("want %d, got %d", want, got)
    }
}

ここでは,Goの標準パッケージの testing を用いて 1 + 2 = 3 になるかをテストしています.

実際に go test コマンドでテストを実行してみると

=== RUN   TestMyadd
--- PASS: TestMyadd (0.00s)
PASS

Program exited.

という結果になり,テストにPASSしていることが確認できます.(Playgroundで動かす
go test -v コマンドを実行することで詳細な情報を出力させることもできます.

テストを別のファイルに書く

テストを行う関数を別の.goファイルに書きたいときは,ファイル名を *_test.go* にはファイル名として有効な文字列が入る)とすることで実装することができます.つまり,先ほどの main.go

main.go
package main

func myadd(a, b int) int {
    return a + b
}

という myadd 関数の実装と

main_test.go
package main

import "testing"

func TestMyadd(t *testing.T) {
    want := myadd(1, 2)
    got := 3 // 1 + 2 = 3 になってほしい
    if want != got {
        t.Errorf("want %d, got %d", want, got)
    }
}

myadd のテストを行う TestmyAdd に分けて実装することもできます.

go-cmp

テストなどで値の比較を行うとき,みなさんは何を使っていますか?? go-cmp,めっちゃ便利なのでぜひ使ってみてください!!簡単に言えば,値の比較をunixの diff コマンド(の -u オプションをつけたもの)に近い形で出力します.

さらに,reflect.Deepequal 関数のように,構造体なども再帰的にチェックしてくれるので == の比較が行えないような場合にも用いることができます.

例えば,int 型整数をフィールドにもつ構造体 MyIntMyInt 型のポインタと int 型整数をフィールドにもつ構造体 MyIntInt をそれぞれ定義してあげます.

type MyInt struct{ N int }
type MyIntInt struct {
    N *MyInt
    M int
}

このような構造体を == で単純に比較しようとすると,同じ値であったとしても false を返すことがあります.

func main() {
    i1 := &MyIntInt{&MyInt{1}, 2}
    i2 := &MyIntInt{&MyInt{1}, 2}
    fmt.Println(i1 == i2) // false
}

しかし,go-cmpdiff であれば再帰的に評価を行うので,このような構造体の比較もきちんと true を返してくれます.もちろん,構造体の値が異なるときは・・・

func main() {
    i1 := &MyIntInt{&MyInt{1}, 2}
    i2 := &MyIntInt{&MyInt{1}, 4}
    fmt.Println(cmp.Diff(i1, i2))
    ...
}

きちんと差分を表示してくれます.

=== RUN   TestMain
  &main.MyIntInt{
        N: &{N: 1},
-       M: 2,
+       M: 4,
  }

Example テスト

Exampleテストとは,サンプルの入力に対する出力を コメントとして 記述しておくことでテストを行うテクニックです.さっき実装した関数 Myadd に対するExampleテストをサクッと実装してみます.

add_test.go
func ExampleMyadd() {
    fmt.Println(myadd.Myadd(1, 2))
    // Output: 3
}

出力のすぐ下の行に // Output: から始まるコメントで想定出力を記載しておくことで 1 + 2 = 3 になるかをテストしています.

これだけだとさっきまでの if want != got のテストと大して変わらないじゃん・・・て思う方も多いかも知れません.Exampleテストの本当に嬉しいところは,godoc でドキュメントを生成したときにExampleテストの内容が実際の例として出力される ことです.そもそも godoc が何かよくわからんて人は 公式ドキュメント などを参照してください.ざっくり説明すると,goのプログラムのドキュメントを生成するコマンドです.

さて,godoc コマンドで実際にMyaddのドキュメントを出力してみると・・・

godocsの出力結果

こんな感じで実際に 1 + 2 = 3 の例が記載されているのが確認できます.マジで便利ですね.

余談ですが,「CIでgodocを実行して,生成した自分用ドキュメントをどこかにプッシュしておく運用」マジでよさそうなので,個人開発で使いたい・・・

まとめ

ということでGoのテストの基礎の基礎についてまとめてみました.「Goはじめて書いてみたけど,テストについてなんもわからん」的な方のお力になれれば幸いです.次回パート2のテーブル駆動テスト編も楽しみにしてください!!

参考資料・各種リンク

Discussion