Eyes, JAPAN Blog > Go による TDD のススメ

Go による TDD のススメ

Taro Aoki

この記事は1年以上前に書かれたもので、内容が古い可能性がありますのでご注意ください。

こんにちは、アルバイトの青木です。

最近、趣味の方で Go をよく使っています。

今回は Go での TDD について紹介したいと思います。

ユニットテストとは

ユニットテストは、関数などの小さな単位での各ロジックが正しく動いているかを検証するためのテストです。
各ロジックが正しく動いていることをテストコードにより証明することで、低レベルでのバグの混入が少なくなり、コードのリファクタリングのような変更にも強くなります。

TDD

TDD とは、Test Driven Development の略称で、ソフトウェアの設計を行ったあと、テストコードを書いて、そのテストが通るように機能を実装していくような開発手法です。
基本的に TDD では、短時間で実装できるようなテストコードを書き、何度も設計→テストコードの作成→実装→リファクタリングのサイクルを繰り返します。
実装、リファクタリングの段階では必ずテストが通らなければいけません。

なぜ Go か

Go の言語仕様はとてもシンプルです。C 言語を勉強したことが有る方ならば、数日あればある程度のコードは書けるようになると思います。
Go の数多くの特徴のうち、二つの特徴を紹介します。
まず、Go には他の言語には必ずと言っていいほど標準で用意されている assert がありません。
assert とは、だいぶ簡単にいえば、true 以外を許容しない if のような構文で、もしも assert に記述した式が false となる場合、エラーが発生します。
他の言語では、このような assert と、テスティングフレームワークを利用してテストコードを書きます。
次に、Go には Go ツールと呼ばれる、標準で用意されているコマンドラインツール群があります。
これには test というツールも含まれていて、 go test とコマンドラインで実行するだけでテストを行うことができます。
つまり、基本的なユニットテストならば Go 単体で完結できてしまうので、Java の JUnit、JavaScript の mocha/chai のようなテスティングフレームワークや assert の構文を覚える必要がありません。

実例

ここでは、簡単な例として、FizzBuzz を返す関数を作成してみましょう。
設計としては、入力値として int 型の値を受け取り、3 で割り切れる場合は “Fizz”、5 で割り切れる場合は “Buzz”、3 と 5 で割り切れる場合は “Fizz Buzz” という文字列を、どれにも該当しない場合は、”nothing” と返すようにします。

まず、FizzBuzz 関数の雛形を作ります。ファイル名は fizzbuzz.go とします。


package main

func FizzBuzz(n int) string {
	return ""
}

次にテストケースを書きます。テスト名は、テスト対象ファイルに _test を付けた形にします。(fizzbuzz_test.go)


package main

import "testing"

func TestFizzBuzz_Fizz(t *testing.T) {
	expect := "Fizz"

	actual := FizzBuzz(3)
	if actual != expect {
		t.Errorf("Expect: %s, Actual: %s", expect, actual)
	}
}

func TestFizzBuzz_Buzz(t *testing.T) {
	expect := "Buzz"

	actual := FizzBuzz(5)
	if actual != expect {
		t.Errorf("Expect: %s, Actual: %s", expect, actual)
	}
}

func TestFizzBuzz_FizzBuzz(t *testing.T) {
	expect := "FizzBuzz"

	actual := FizzBuzz(15)
	if actual != expect {
		t.Errorf("Expect: %s, Actual: %s", expect, actual)
	}
}

func TestFizzBuzz_Nothing(t *testing.T) {
	expect := "Nothing"

	actual := FizzBuzz(16)
	if actual != expect {
		t.Errorf("Expect: %s, Actual: %s", expect, actual)
	}
}

ここではそれぞれのパターンごとに4つのテストケースを書いています。
この状態で go test と実行してみると以下のようになります。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-11-28-18-01-12

エラーメッセージを読んでみると、4つのテストケースがすべて失敗しているのがわかります。

次に、テストケースがすべて通るように最小限の変更を加えてみましょう。


package main

func FizzBuzz(n int) string {
	switch n {
	case 3:
		return "Fizz"
	case 5:
		return "Buzz"
	case 15:
		return "FizzBuzz"
	case 16:
		return "Nothing"
	}

	return ""
}

この状態でテストを実行すると、
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-11-28-18-13-39

無事テストが通りました。
今度はこれを実際のコードに直しましょう。ここで、変更してもテストが通るように気をつけます。


package main

func FizzBuzz(n int) string {
	fizz := n%3 == 0
	buzz := n%5 == 0

	switch {
	case fizz && buzz:
		return "FizzBuzz"
	case fizz:
		return "Fizz"
	case buzz:
		return "Buzz"
	default:
		return "Nothing"
	}
}

最終的な形はこのようになりました。テストを実行して正しく動くことを確認します。
このサンプルはこれで終わりです。

また、Go ツールにはカバレッジを測定するためのツールも用意されており、以下のように実行すると関数毎にカバレッジが分かります。
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-11-28-18-32-10

まとめ

このように Go では、Go 単体だけで、かつテスト用の構文をほとんど利用せずにテストを書くことができます。
学習コストが非常に少ないと思いますので、ぜひ一度触ってみてはいかがでしょうか?

Comments are closed.