どうも、Go勉強中のとがみんです。
より品質の良いプログラムを書くにはどうしたら良いのか?を考える中で、テスト駆動開発というものを知りました。
この記事では、テスト駆動開発とは何か、またGo言語を通してテスト駆動開発を試しにやってみて、それを整理していこうかと思います。
Contents
テスト駆動開発とは?
テスト駆動開発とは、ソフトウェア開発手法の一つで、先にテストコードを書き、そのテストに通るように本体のコードを記述していく方式です。
テスト駆動開発により、リファクタリングや機能追加に対するリスクが低減され、リファクタリングすることが容易になります。
そのため、綺麗なコードを維持することができるようになり、結果、機能改修や機能追加のスピードが早くなることが一番のメリットだと考えています。
Go言語でテスト駆動開発を試してみる
作成するもの
Hello関数を作成していきますます。
下記の機能を順番にテスト駆動開発を通して実現していきます。
- Hello関数を実行すると「Hello World!」が表示される。
- 引数にnameを指定すると、「Hello 〇〇 !」のように挨拶の宛先を指定できる。
Hello関数のテストを作成する
hello_test.goのファイルを作成し、下記のようにテストコードを記載します。
1 2 3 4 5 6 7 8 9 10 11 | package hello import "testing" func TestHello(t *testing.T){ got := Hello() want := "Hello World !" if got != want { t.Errorf("got %q want %q", got, want) } } |
Go言語において、テストファイルやテスト関数の作成においては下記のようなルールがあります。
- テストファイルは、xxx_test.goのような名前のファイルにする。
- テスト関数はTestという単語から始める。
- テスト関数は、t *testing.Tという1つの引数のみをとる。
- t *testing.T型を使うために、import “testing”が必要になる。
書いたテストコードを実行する
まだ、テストコードしか作成していませんが、テストを実行します。
go testコマンドを実行し、テストします。
go: go.mod file not found in current directory or any parent directory; see ‘go help modules’
テスト結果後上記のようなエラーが出力されました。
go.modというファイルが存在しないためエラーが出力されました。
go.modファイルは、Go言語の、依存モジュールを管理するツールになります。
このファイルがないとテストが実行できないため、追加します。
go.modを追加し再度テスト実行する
go.modを追加するためには、下記のコマンドを実行します。
実行すると下記のgo.modというファイルが追加されます。
1 2 3 4 | code:go.mod module hello go 1.18 |
追加後go testコマンドを実行しテストします。
# hello [hello.test]
./hello_test.go:6:9: undefined: Hello
FAIL hello [build failed]
すると、
が定義されていないとエラーが出力されました。
書いていないので当然のエラーです。このHello関数を次に書いていきます。
Hello関数を作成してテストを再実行する
hello.goというファイルを作成し、下記のような関数を書きます。
1 2 3 4 5 | package hello func Hello() string{ return "Hello World !" } |
Hello関数を作成した後に、テストを再実行します。
PASS
ok hello 0.968s
テストが通ったことが確認できました。
テストが通ったらGitコミットする
テストが通ったらGitにコミットします。
今回は簡易なプログラムですが、通常はテストが通るコードを記載した後に、リファクタリングを行います。
リファクタリングをする中で、ソースコードが混乱してしまった場合に、テストが通る状態まで戻すことができるように、テストが通ったタイミングでコミットするのが良さそうです。
宛先を指定する機能追加のためのテストコードを作成しテスト実行する
hello_test.goを修正します。
下記のように、引数に挨拶の宛先を指定し、実行すると「Hello 〇〇 !」という出力が期待されるテストコードに変更します。
今回は引数に「togamin」と指定し「Hello togamin !」という出力を期待するテストコードを書きました。
1 2 3 4 5 6 7 8 9 10 11 | package hello import "testing" func TestHello(t *testing.T){ got := Hello("togamin") want := "Hello togamin !" if got != want { t.Errorf("got %q want %q", got, want) } } |
テストコード変更後テストを実行します。
# hello [hello.test]
./hello_test.go:6:15: too many arguments in call to Hello
have (string)
want ()
FAIL hello [build failed]
テストを実行すると、too many arguments in call to Helloと、引数の数が多すぎるというコンパイルエラーが表示されました。
このエラーが解消されるようにソースコードを修正します。
Hello関数に引数を指定できるようにしテストを再実行する
下記のように引数を指定できるように修正しました。
1 2 3 4 5 | package hello func Hello(name string) string{ return "Hello World !" } |
テストを再実行します。
— FAIL: TestHello (0.00s)
hello_test.go:9: got “Hello World !” want “Hello togamin !”
FAIL
exit status 1
FAIL hello 0.518s
今度はコンパイルは通りましたが、Hello関数の出力結果が「Hello World !」で、期待結果が「Hello togamin !」のため、テストが失敗しました。
期待結果になるようにソースコードを修正します。
コードを修正しテストを再実行する
下記のようにソースコードを修正し、引数に与えた文字列が、挨拶に組み込まれるようにしました。
1 2 3 4 5 | package hello func Hello(name string) string{ return "Hello " + name + " !" } |
修正後再度テストを実行します。
PASS
ok hello 0.492s
テストが通ったことを確認したので、GItへコミットしておきます。
リファクタリングする
テストが通ったら次はリファクタリングをします。
今回は、出力する文字列を定数として切り出します。
1 2 3 4 5 6 7 8 | package hello const helloPrefix = "Hello " const exclamation = " !" func Hello(name string) string{ return helloPrefix + name + exclamation } |
リファクタリング後、テストを再実行します
PASS
ok hello 0.547s
テストが通ったので、Gitにコミットします。
テスト駆動開発の流れ
上記にテスト駆動での開発の流れを試してみました。
テスト駆動開発では以下のようなサイクルを回しながらソースコードを記載していきます。
- テストコードを書く。
- コンパイラーをパスする。
- テストを実行し、失敗することを確認し、テストのエラーコードが正常に出力されることを確認する。
- テストを合格するためのコードを記述する。
- Gitへコミットしテストが通過した状態へ戻れるようにする。
- リファクタリングする。
上記のようなサイクルを通して書くことによって、テストコードを見れば実装されている要件を確認することができ、テストに信頼性が生まれ、リファクタリングしやすく綺麗なコードを維持できるようになり、開発生産性を向上させることができるようになります。
まとめ
Go言語でテスト駆動開発の一連のプロセスについて紹介してきました。