Skip to content

テストを書いてみよう

テストを書く前に、テスト対象になる処理が必要です。今回は、「与えられた City のリストから国ごとの人口の和」を計算する処理を書いてみます。

ヒント
  • 国ごとにデータを分けて持つにはmapを使えばいいでしょう
  • 国単位で集計するので map の key は CountryCode を使うといいでしょう
  • データが入っていない場合もあるので、条件分岐には気を付けてください
参考実装
go
func sumPopulationByCountryCode(cities []City) map[string]int64 {
	result := make(map[string]int64)
	for _, city := range cities {
		if city.CountryCode.Valid {
			// まだmapに存在しなかった場合、初期化する
			if _, ok := result[city.CountryCode.String]; !ok {
				result[city.CountryCode.String] = 0
			}
			result[city.CountryCode.String] += city.Population.Int64
		}
	}
	return result
}

そうしたら、このコードが期待した値を返すかテストを書いてみましょう。

まず、calculate_test.goを作成します。

TIP

Go では、_testがファイル名の後ろについているファイルはテストファイルとして認識されます。

続いて、calculate_test.goにテスト関数を実装していきます。

go
package main

import "testing"
// Testで始まる関数はテスト関数として認識されます
// testingはGoのテストのための標準ライブラリです
func Test_sumPopulationByCountryCode(t *testing.T) {
    // ここにテストを書いていく
}

まずは、空のリストを渡したときに、空のマップが返ってくることをテストしてみましょう。

go
package main

import "testing"

func Test_sumPopulationByCountryCode(t *testing.T) {
	// ここにテストを書いていく
	cities := []City{}
	got := sumPopulationByCountry(cities)
	want := map[string]int{}
	// 長さが0になっているかどうかを確認する
	if len(got) != 0 {
		t.Errorf("sumPopulationByCountryCode(%v) = %v, want %v", cities, got, want)
	}
}

書き終わったら、関数の左上にあるrun testか、そのさらに左にある再生ボタンを押して、テストを実行してみましょう。

すると、VSCode の Output にテストの結果が表示されます。

=== RUN   Test_calculatePopulation
--- PASS: Test_calculatePopulation (0.00s)
PASS
ok  	test	0.001s

テストが正常に終了したことがわかりますね。

様々なケースをテストしてみよう

様々なケースをテストするために使われるテストの方法として、テーブル駆動テスト (Table Driven Tests) というものがあります。 これは、入力と求める出力を一緒に書いたテストケースをテーブルとして用意しておく方法です。

具体的にコードを見てみましょう。

go
package main

import (
	"maps"
	"testing"
)

func Test_sumPopulationByCountryCode(t *testing.T) {
	// ここにテストケースを書いていく
	cases := []struct {
		name   string           // テストケースの名前
		cities []City           // テストケースの入力
		want   map[string]int64 // 期待される結果
	}{
		{
			name:   "empty input",
			cities: []City{},
			want:   map[string]int64{},
		},
	}
	for _, tt := range cases {
		// サブテストの実行
		t.Run(tt.name, func(t *testing.T) {
			got := sumPopulationByCountryCode(tt.cities)
			if !maps.Equal(got, tt.want) {
				t.Errorf("sumPopulationByCountryCode(%v) = %v, want %v", tt.cities, got, tt.want)
			}
		})
	}
}

テーブル駆動テストのメリットは、テストケースの追加が簡単なことです。 新しくテストケースを追加するには、casesに追加するだけです。

課題

次のテストを実装してください。

  • 1 つの国のみのデータが入っている場合
  • 複数の国のデータが入っている場合
  • 空のデータ(city.CountryCode.Valid = false)が入っている場合
答え

casesに追加するものだけ示しています。

1 つの国のみのデータが入っている場合

go
{
	name: "single country",
	cities: []City{
		{
			CountryCode: sql.NullString{
				String: "JPN",
				Valid:  true,
			},
			Population: sql.NullInt64{
				Int64: 100,
				Valid: true,
			},
		},
	},
	want: map[string]int64{"JPN": 100},
},

複数の国のデータが入っている場合

go
{
	name: "multiple countries",
	cities: []City{
		{
			CountryCode: sql.NullString{
				String: "JPN",
				Valid:  true,
			},
			Population: sql.NullInt64{
				Int64: 100,
				Valid: true,
			},
		},
		{
			CountryCode: sql.NullString{
				String: "USA",
				Valid:  true,
			},
			Population: sql.NullInt64{
				Int64: 200,
				Valid: true,
			},
		},
	},
	want: map[string]int64{"JPN": 100, "USA": 200},
},

空のデータ(city.CountryCode.Valid = false)が入っている場合

go
{
	name: "empty country code",
	cities: []City{
		{
			CountryCode: sql.NullString{
				String: "",
				Valid:  false,
			},
			Population: sql.NullInt64{
				Int64: 100,
				Valid: true,
			},
		},
	},
	want: map[string]int64{},
},

最終的なテストケース

go
cases := []struct {
	name   string
	cities []City
	want   map[string]int64
}{
	{
		name:   "empty input",
		cities: []City{},
		want:   map[string]int64{},
	},
	{
		name: "single country",
		cities: []City{
			{
				CountryCode: sql.NullString{
					String: "JPN",
					Valid:  true,
				},
				Population: sql.NullInt64{
					Int64: 100,
					Valid: true,
				},
			},
		},
		want: map[string]int64{"JPN": 100},
	},
	{
		name: "multiple countries",
		cities: []City{
			{
				CountryCode: sql.NullString{
					String: "JPN",
					Valid:  true,
				},
				Population: sql.NullInt64{
					Int64: 100,
					Valid: true,
				},
			},
			{
				CountryCode: sql.NullString{
					String: "USA",
					Valid:  true,
				},
				Population: sql.NullInt64{
					Int64: 200,
					Valid: true,
				},
			},
		},
		want: map[string]int64{"JPN": 100, "USA": 200},
	},
	{
		name: "empty country code",
		cities: []City{
			{
				CountryCode: sql.NullString{
					String: "",
					Valid:  false,
				},
				Population: sql.NullInt64{
					Int64: 100,
					Valid: true,
				},
			},
		},
		want: map[string]int64{},
	},
}