LoginSignup
64
29

最近テスト駆動開発を勉強し始めました。
本記事では、Goで簡単なサンプルをテスト駆動開発で実装していきます。

テスト駆動開発とは

こちらの本でテスト駆動開発を勉強中です。

テスト駆動開発がどんなものであるか、詳しくは上記の本や別記事を参照いただければと思いますが、簡単に言うと以下を繰り返して開発する手法です。

  1. 失敗するテストを書く(この時点ではテストは失敗する)
  2. 最小限の変更でテストを通るコードを書く(まずは通れば良い)
  3. きれいなコードにする: リファクタリング(テストがパスする状態を維持しながら、意味のあるコードに改善する)

これによって以下のメリットが得られます。

  • 仕様への理解が深まる
  • 不具合・バグの早期発見につながる
  • 開発者の心理的負担が軽減する

準備

Goでテスト駆動開発をするための準備をします。

まずはモジュールの作成

go mod init tdd-sample

次にtestifyを導入します。

go get github.com/stretchr/testify

作るもの

今回作るのはCatクラスです。

注) Goにはクラスという概念は無く、構造体やメソッドでクラスっぽいことをします。本記事では便宜上「クラス」と呼びたいと思います。

  • Catにはメンバ変数としてsoundがある
  • soundを返すメソッドとしてCry()がある

Catクラスの作成

それではCatクラスを作っていきましょう。

...と焦ってはいけません。今回はテスト駆動開発です。
最初にやるべきはテストを書くことです。

まずはテストを書きます。

1. 失敗するテストを書く

pet_test.go
package main_test

import (
	"github.com/stretchr/testify/assert"
	main "tdd-sample"
	"testing"
)

func TestCry(t *testing.T) {
	c1 := main.NewCat("ニャーニャー")
	assert.Equal(t, "ニャーニャー", c1.Cry())
}

それではテストをしましょう。

% go test
# tdd-sample_test [tdd-sample.test]
./pet_test.go:10:13: undefined: main.NewCat
FAIL	tdd-sample [build failed]

当然、失敗します。というかコンパイルすらできていません。
まずはコンパイルできるようにしましょう。ここで大事なのは中身はともかくコンパイルできれば良いということです。

テスト結果はNewCatというメソッドがないと言っています。作りましょう。

cat.go
package main

func NewCat() {
}

「中身なにも無くていいの?」と思われるかもしれませんが、小さくステップを踏んでいくことがテスト駆動開発では大事です。
テストを実行しましょう。

% go test
# tdd-sample_test [tdd-sample.test]
./pet_test.go:10:8: main.NewCat("ニャーニャー") (no value) used as value
./pet_test.go:10:20: too many arguments in call to main.NewCat
	have (string)
	want ()
FAIL	tdd-sample [build failed]

以下の2点を指摘されています。

  • (no value) used as valueNewCat関数が何もreturnしないため、関数が値として利用されている
  • NewCat関数は引数を取らない

直していきましょう。

ここでそもそもNewCat関数は何をしたいのでしょうか?
以下2点ですね。

  • 鳴き声を引数にとる
  • Catという構造体(正確にはそのポインタ)を返す

変更を最小限に実装します。

cat.go
package main

type Cat struct {
}

func NewCat(sound string) *Cat {
	return &Cat{}
}

テストを実行しましょう。

% go test
# tdd-sample_test [tdd-sample.test]
./pet_test.go:11:43: c1.Cry undefined (type *main.Cat has no field or method Cry)
FAIL	tdd-sample [build failed]

一歩前進しました!
まだコンパイルエラーはありますがあと一歩です。

次はCryメソッドがないと言っています。実装しましょう。

cat.go
package main

type Cat struct {
}

func NewCat(sound string) *Cat {
	return &Cat{}
}

func (c *Cat) Cry() string {
	return ""
}

テストを実行しましょう。

% go test
--- FAIL: TestCry (0.00s)
    pet_test.go:11:
        	Error Trace:	/Users/takasaki.kazunari/work/go-sample/pet_test.go:11
        	Error:      	Not equal:
        	            	expected: "ニャーニャー"
        	            	actual  : ""

        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -1 +1 @@
        	            	-ニャーニャー
        	            	+
        	Test:       	TestCry
FAIL
exit status 1
FAIL	tdd-sample	0.136s

ついにコンパイルエラーが無くなりました!

テストは失敗していますが、これで良いです。
1. 失敗するテストを書くが完了しました!

2. 最小限の変更でテストを通るコードを書く

続いて2つ目のステップです。
ここで大事なのは、とにかくテストを通すことです。

前回のテストの結果はこうです。

Error:      	Not equal:
        	            	expected: "ニャーニャー"
        	            	actual  : ""

pet_test.goの11行目assert.Equal(t, "ニャーニャー", c1.Cry())で、c1.Cry()"ニャーニャー"と返してほしかったのですが、実際には空文字""を返しています。

このテストを通すのに必要なことは、c1.Cry()"ニャーニャー"と返すことです。
それでは、こうしましょう。

cat.go
package main

type Cat struct {
}

func NewCat(sound string) *Cat {
	return &Cat{}
}

func (c *Cat) Cry() string {
	return "ニャーニャー"
}

テストを実行します。

% go test
PASS
ok  	tdd-sample	0.136s

やった!!初めてテストが通りました!!

いやいや、それはあまりに無理やりでは? と思うかもしれません。

しかしこれで良いのです。これが2. 最小限の変更でテストを通るコードを書くのポイントです。
本当は、soundというメンバ変数を持って、その中身をCry()で返す…としたいですよね。
焦ってはいけません。それは次の3. きれいなコードにする: リファクタリングでやることです。
今は、これでOKです。

3. きれいなコードにする: リファクタリング

いよいよコードをきれいにしていきます。
ここでは、少しずつステップを踏んでいきましょう。

まずは、メンバ変数soundを作ります。

cat.go
package main

type Cat struct {
    sound string
}

func NewCat(sound string) *Cat {
	return &Cat{}
}

func (c *Cat) Cry() string {
	return "ニャーニャー"
}

テストを実行します。(少しの変更でも、毎回テストをするのが大事です。)
まだ通りますね。
続いて、NewCatでメンバ変数soundに値をセットしましょう。

cat.go
package main

type Cat struct {
    sound string
}

func NewCat(sound string) *Cat {
	return &Cat{sound: "ニャーニャー"}
}

func (c *Cat) Cry() string {
	return "ニャーニャー"
}

テストをします。まだ大丈夫。
Cry関数で、メンバ変数を返すようにしましょう。

cat.go
package main

type Cat struct {
    sound string
}

func NewCat(sound string) *Cat {
	return &Cat{sound: "ニャーニャー"}
}

func (c *Cat) Cry() string {
	return c.sound
}

テストをします。
OK!順調です。

重複を除去する

ここでNewCatをよく見てみると、soundに直接"ニャーニャー"をセットしています。
テストではc1 := main.NewCat("ニャーニャー")としているので、引数soundにも"ニャーニャー"が入っています。
これを重複といいます。
リファクタリングではこうした重複を除去していきます。

最後に、こうしましょう。

cat.go
package main

type Cat struct {
    sound string
}

func NewCat(sound string) *Cat {
	return &Cat{sound: sound}
}

func (c *Cat) Cry() string {
	return c.sound
}

テストをします。

% go test
PASS
ok  	tdd-sample	0.478s

テストが通りました!
リファクタリング完了です。

さいごに

今回は、非常に簡単なお題で、Goを使ったテスト駆動開発を実践しました。
ここで実践したものは、テスト駆動開発のほんの一部です。
まだまだ勉強途中なので、引き続き実践を続けていきます。

64
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
64
29