📚

Golangでテストコードに入門してみた

に公開1

今回はGolangでのテストコードに入門してみました。私自身普段あまりGolangは使ってこなかったのですが、今回使う気概があり、テストコードが必要だったので入門してみました。

早速やってみる

シンプルな例で。。。

まずはプロジェクトの初期化をします。goenvを使っているので、以下のようにして環境を立てました。

mkdir go_test_prac && cd go_test_prac
goenv local 1.22.3

まずはシンプルな四則演算を実装しているコードを作成しました。

func.go
package main

import "fmt"

func Add(x int, y int) (int, error) {
	return x + y, nil
}

func Sub(x int, y int) (int, error) {
	return x - y, nil
}

func Mul(x int, y int) (int, error) {
	return x * y, nil
}

func Div(x int, y int) (float64, error) {
	if y == 0 {
		return 0, fmt.Errorf("division by zero")
	}
	return float64(x) / float64(y), nil
}

このコードはシンプルな四則演算を実装しており、Div関数では0割を検知するための機能を入れています。

こちらに対応するテストコードを以下のように実装しました。

func_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
	x := 1
	y := 2
	expected := x + y
	result, _ := Add(x, y)

	if result != expected {
		t.Errorf("Add(1, 2) = %d; want %d", result, expected)
	}
}

func TestSub(t *testing.T) {
	x := 1
	y := 2
	expected := x - y
	result, _ := Sub(x, y)

	if result != expected {
		t.Errorf("Sub(1, 2) = %d; want %d", result, expected)
	}
}

func TestMul(t *testing.T) {
	x := 1
	y := 2
	expected := x * y
	result, _ := Mul(x, y)

	if result != expected {
		t.Errorf("Mul(1, 2) = %d; want %d", result, expected)
	}
}

func TestDiv(t *testing.T) {
	x := 1
	y := 2
	expected := float64(x) / float64(y)
	result, _ := Div(x, y)

	if result != expected {
		t.Errorf("Div(1, 2) = %f; want %f", result, expected)
	}
}

func TestDivZeroDivision(t *testing.T) {
	x := 1
	y := 0
	_, err := Div(x, y)

	if err == nil {
		t.Error("0 division must be raised")
	}
}

計算結果が正しく帰ってくることを確認する実装になっており、Div関数において分母の数字が0とそうでない場合とで別に実装しています。

このコードをgo test ./...で実行すると、以下のような結果が得られました。

# go test ./...
ok  	go_test_prac	0.296s

これより、テスト全てが正常終了し、所要時間は0.296秒であったということです。この四則演算レベルのテストはすごいシンプルなので簡単な実装で済みました。

パラメータでテストケースを複数作成

先ほどのテストケースでは、テストごとにパラメータセットを一つずつ設定していましたが、複数のパラメータセットでテストを動かしたい場合、以下のようにするとできるようです。

func TestAddMultipleCases(t *testing.T) {
	tests := []struct {
		name     string
		a, b     int
		expected int
	}{
		{"2+3", 2, 3, 5},
		{"0+0", 0, 0, 0},
		{"-1+1", -1, 1, 0},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			result, _ := Add(test.a, test.b)
			if result != test.expected {
				t.Errorf("Add(%d, %d) = %d; want %d", test.a, test.b, result, test.expected)
			}
		})
	}
}

上記のように、構造体でパラメータセットを定義してそれをfor分で回しながらテストを実行することで、パラメータを複数ペア用意してテストが実行されます。これを実行すると無事テストが成功します。ちなみに、テスト実行時にgo test -vのように-vオプションをつけると結果でテストケース名などが取得できます。

# go test -v ./...
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   TestSub
--- PASS: TestSub (0.00s)
=== RUN   TestMul
--- PASS: TestMul (0.00s)
=== RUN   TestDiv
--- PASS: TestDiv (0.00s)
=== RUN   TestDivZeroDivision
--- PASS: TestDivZeroDivision (0.00s)
=== RUN   TestAddMultipleCases
=== RUN   TestAddMultipleCases/2+3
=== RUN   TestAddMultipleCases/0+0
=== RUN   TestAddMultipleCases/-1+1
--- PASS: TestAddMultipleCases (0.00s)
    --- PASS: TestAddMultipleCases/2+3 (0.00s)
    --- PASS: TestAddMultipleCases/0+0 (0.00s)
    --- PASS: TestAddMultipleCases/-1+1 (0.00s)
PASS
ok  	go_test_prac	0.188s

このように、新しく追加したテストケースはテスト内で複数のテストを回していますが、それが正確に記録されていることが確認できました。

カバレッジのチェック

go testでカバレッジをチェックするには-cオプションをつければいいようです。また、-coverprofile=cover.outのようにすることでカバレッジレポートをcover.outに出力できるようです。また、go tool cover -html=cover.outのようにすることでブラウザ上でカバレッジチェックができるようになります。試しに、func_test.goからSub関数に関するテストを削除する前とあとで比較してみました。期待通り、Sub関数のテストを省略した場合のカバレッジが低くなり、それに合わせてUI上で確認できる結果でもSub関数のテストがされていないことが確認できます。

  • Sub関数のテストあり:coverage=85.7%

  • Sub関数のテストなし:coverage=71.4%

まとめ

今回はGolangを使ってテストを実装するということに入門してみました。特にパラメータのペアを構造体で定義してループして実行する部分は、pytest.mark.parametrizeと似たような感じでできて、Pythonの経験が割とそのまま生きた気がします。今後はもっと複雑な機能についてもテスト実装をしてみたいと思います。

Discussion

AkasanAkasan

Subテストありのカバレッジ画像がなぜか表示されないですが、二つ目の画像で赤くなっている場所が全て緑色になっている画像となります