テストを書いてみよう
テストを書く前に、テスト対象になる処理が必要です。今回は、「与えられた 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{},
},
}