Goのテスト作成とカバレッジ率＆カバレッジ行表示をしてみる

**少なくともpublicなメソッドや関数のテストケースがないと、ロクにリファクタもできず、機能追加ではデグレが生まれ、エンジニアのモチベーションが下がり続けるというのは世界の理（ことわり）。**

まあでもレガシーシステムが横行する世知辛い現実業務では【テストコードが無い】状態なんて往々にしてあるわけで…

- 「動いたものが仕様！な世紀末」状態
- 「短納期で要件がコロコロ変わりテストケース書く前にリリースになっちゃったよ！」状態
- 「結合試験はやったし余裕があるときにテストコード書こう～そして余裕はこない～」状態
- 「カバレッジに拘った結果膨大になりすぎたテストケースの保守工数が逆にコストになりすぎて放棄」状態
- 「誰も触りたくない改修したら負けなレガシーシステム」状態
 
理由は様々。
それでも踏ん張ってテストコードは書かないと、苦しむのは未来の自分達。
なんだか泣けてきた…。

本題からズレましたが、Goも触り始めて2週間が経過し [ちょうどいいロジック](http://qiita.com/silverfox/items/688c304f7b76c5230d40)もできたので今後はテストケースを書いていこうと思います。

##今回やりたいこと

1. goのテストコードを書いてみる
2. `go test`でテストの実行をしてみる
3. **未カバレッジの行を特定し、カバレッジ率を100％にしてみる**

##前提条件

- 過去の記事「[WindowsのEclipseでGo+AppEngineの開発環境を構築](http://qiita.com/silverfox/items/3a50cafc44c25e8c52e3 "WindowsのEclipseでGo+AppEngineの開発環境を構築")」で構築した環境を利用しています。
- 前回作った「[Go言語でn元連立1次方程式の解と逆行列を求めてみる(掃き出し法編)](http://qiita.com/silverfox/items/688c304f7b76c5230d40)」が**テスト対象です。**

パッケージ構成は以下。
![ecliseの設定_07.PNG](https://qiita-image-store.s3.amazonaws.com/0/144652/e3b0fcbf-b73f-0e96-5655-d13fb121066d.png)


##コーディング開始

### fox/matrix/matrix_text.go

`matrix.go`のテストケース。
本来は引数のindexがlengthを超えているパターン等も用意すべきですが、そもそも実装側でケアできていないので正常系のみのケースです。

```go:fox/matrix/matrix_text.go
package matrix

import "testing"

func TestMultiplication(t *testing.T) {
	testCases := []struct {
		left   [][]float64
		right  [][]float64
		result [][]float64
	}{
		{
			[][]float64{{1, 5, 9}},
			[][]float64{{5, 2}, {3, 1}, {4, -1}},
			[][]float64{{56, -2}},
		},
		{
			[][]float64{{1, 5, 9}},
			[][]float64{{8, 4, 2}, {1, 3, -6}, {-7, 0, 5}},
			[][]float64{{-50, 19, 17}},
		},
		{
			[][]float64{{5}, {2}},
			[][]float64{{1, 5, 9}},
			[][]float64{{5, 25, 45}, {2, 10, 18}},
		},
	}

	for _, tc := range testCases {
		result := Multiplication(tc.left, tc.right)
		if !equals(result, tc.result) {
			t.Errorf("not equals. %#v %#v", result, tc.result)
		}
	}
}

func TestDeepCopy(t *testing.T) {
	testCases := []struct {
		matrix [][]float64
	}{
		{ //1行しかないパターン
			[][]float64{{1, 5, 9}},
		},
		{ //3×3の行列
			[][]float64{{8, 4, 2}, {1, 3, -6}, {-7, 0, 5}},
		},
	}

	for _, tc := range testCases {
		result := DeepCopy(tc.matrix)
		if !equals(result, tc.matrix) {
			t.Errorf("not equals. %#v %#v", result, tc.matrix)
		}
	}
}

func TestCreateRamdomMatrix(t *testing.T) {
	num := []int{5, 5}
	matrix := CreateRamdomMatrix(num)

	valid := bool(true)
	if len(matrix) != num[0] {
		valid = false
	}
	for i := range matrix {
		if len(matrix[i]) != num[1] {
			valid = false
		}
	}
	if !valid {
		t.Errorf("invalid.")
	}
}

func TestCreateUnitMatrix(t *testing.T) {
	num := 5
	matrix := CreateUnitMatrix(num)

	valid := bool(true)
	for i := range matrix {
		for n := range matrix[i] {
			if i == n && matrix[i][n] != 1 {
				valid = false
			} else if i != n && matrix[i][n] != 0 {
				valid = false
			}
		}
	}
	if !valid {
		t.Errorf("invalid.")
	}
}

func TestReplaceMatrixRow(t *testing.T) {
	testCases := []struct {
		matrix [][]float64
		index1 int
		index2 int
		result [][]float64
	}{
		{ //入れ替わらないパターン
			[][]float64{{5, 2}, {3, 1}, {4, -1}},
			1,
			1,
			[][]float64{{5, 2}, {3, 1}, {4, -1}},
		},
		{ //入れ替わるパターン
			[][]float64{{5, 2}, {3, 1}, {4, -1}},
			0,
			2,
			[][]float64{{4, -1}, {3, 1}, {5, 2}},
		},
	}

	for _, tc := range testCases {
		ReplaceMatrixRow(tc.matrix, tc.index1, tc.index2)
		if !equals(tc.matrix, tc.result) {
			t.Errorf("not equals. %#v %#v", tc.matrix, tc.result)
		}
	}
}

func equals(matrix1 [][]float64, matrix2 [][]float64) bool {
	if len(matrix1) != len(matrix2) {
		return false
	}
	for i := range matrix1 {
		if len(matrix1[i]) != len(matrix2[i]) {
			return false
		}
		for n := range matrix1[i] {
			if matrix1[i][n] != matrix2[i][n] {
				return false
			}
		}
	}
	return true
}
```

### fox/matrix/gaussian_test.go

`gaussian_test.go`のテストケース。
こちらも「カバレッジを100％にする」ことを目的とした正常系のみのテストケースになります。
※本来は正則でない行列のパターンとかも追加すべき
<font color="Red">また、どうしても丸め誤差が生じてしまうため、完全一致での比較ではなく、0.00000001差を許容するニアリーイコールでの比較にしました。</font>

```go:fox/matrix/gaussian_test.go
package matrix

import (
	"math"
	"testing"
)

func TestSolve(t *testing.T) {

	testCases := []struct {
		left    [][]float64
		right   [][]float64
		result  [][]float64
		inverse [][]float64
	}{
		{
			[][]float64{{3, 2}, {7, 5}},
			[][]float64{{1}, {-1}},
			[][]float64{{7}, {-10}},
			[][]float64{{5, -2}, {-7, 3}},
		},
		{
			[][]float64{{1, 2, 0}, {3, -1, 1}, {2, 1, 1}},
			[][]float64{{3}, {0}, {1}},
			[][]float64{{1}, {1}, {-2}},
			[][]float64{{0.5, 0.5, -0.5}, {0.25, -0.25, 0.25}, {-1.25, -0.75, 1.75}},
		},
		{
			[][]float64{{2, 1, -1}, {1, -1, 2}, {-2, 3, 1}},
			[][]float64{{1}, {5}, {7}},
			[][]float64{{1}, {2}, {3}},
			[][]float64{{0.35, 0.2, -0.05}, {0.25, 0, 0.25}, {-0.05, 0.4, 0.15}},
		},
	}
	for _, tc := range testCases {
		ge := NewGaussJordanElimination(tc.left, tc.right)
		if !nearlyEquals(ge.Solve(), tc.result) {
			t.Errorf("not equals. %#v %#v", ge.RfsMatrix, tc.result)
		}
		if !nearlyEquals(ge.InverseMatrix, tc.inverse) {
			t.Errorf("not equals. %#v %#v", ge.InverseMatrix, tc.inverse)
		}
	}
}

func nearlyEquals(matrix1 [][]float64, matrix2 [][]float64) bool {
	if len(matrix1) != len(matrix2) {
		return false
	}
	for i := range matrix1 {
		if len(matrix1[i]) != len(matrix2[i]) {
			return false
		}
		for n := range matrix1[i] {

			if matrix1[i][n] != matrix2[i][n] && math.Abs(matrix1[i][n]-matrix2[i][n]) > 0.00000001 {
				return false
			}
		}
	}
	return true
}
```

## テストの実行

**いきなりハマりました。**Eclipseでのテスト実行がうまくいかず、今回は断念。
あきらめてコンソールでの実行にしました。
~~やっぱJava以外ではEclipseクソだわ~~

fox/matrixパッケージを対象にテストの実行を行ってみます。
コマンドプロンプトから実行する場合、eclipseの該当プロジェクトをGOPATHに追加する必要があるので気を付けてください。
setで追加しているためプロンプトを落とせばGOPATHは元に戻ります。

※下記{eclipse_project} は筆者の場合はC:\works\eclipse\workspace\matrix

```:コマンドプロンプトで実行
set GOPATH=%GOPATH%;"{eclipse_project}"
cd "{eclipse_project}"\src
go test -coverprofile=cover.out  fox\matrix
```

上記を実行すると以下のように表示されます。

```:
ok      fox/matrix      0.099s  coverage: 98.5% of statements
```

カバレッジ率98.5%。どうやら通過していない行が存在するようです。
**カバレッジツールを利用して未通過行を特定してみます。**

### カバレッジツールの利用

goのライブラリとしてtoolが用意されていたためそちらを利用しました。
``go test``を実行する際に、`-coverprofile=cover.out`というオプションを付けていたのがミソです。
実行した際のカレントディレクトリにcover.outというファイルができています。
このファイルをツールで読み込みます。

```:コマンドプロンプトから実行
go tool cover -html=cover.out
```
折角なのでHTMLで表示。（HTMLだとかなり見やすいです）
デフォルトブラウザで以下のように表示されます。

![test_01.PNG](https://qiita-image-store.s3.amazonaws.com/0/144652/37878e82-686c-4e01-ca81-2738848171ea.png)

この赤い行がテスト未通過のコードです。
調べてみたら、実装時にリファクタした結果「絶対にif文が成立しない不要コード」となっていたため、削除しました。

***やっぱ自動テストは最高ですね！***
このコードは色々機能追加予定なので安心してリファクタが行えます。

### そして黄金郷（エル・ドラド）へ

上記の不要コードを削除し、テストコードを再実行。

```
ok      fox/matrix      0.158s  coverage: 100.0% of statements
```

きたーカバレッジ100%！！

まあ100%なんて夢を見れるのはこの程度の小さなプログラムだけですね。
Javaとかだと例えプログラムが小さくてもcatch内のException等で諦めるしかない時もあるし。

## 感想

とりあえず、Goでテストコードを書いてみた感想ですが…***何も便利なものがないですね笑***
まあそもそも言語自体がシンプルなので高機能なテストツール自体不要なのかもしれません。
これから先使い続けると色々見えててきそう、かな。

～ではまた次回～