Go で テストカバレッジを見える化の巻

この記事は[Ateam Hikkoshi Samurai Inc. & Ateam Connect Inc.（エイチーム引越し侍、エイチームコネクト） Advent Calendar 2019](https://qiita.com/advent-calendar/2019/hikkoshi) 12日目の記事になります。

## はじめに

どーも、まいどおおきにおはこんにちこんばんは。

現在、エイチーム引越し侍では、2019年6月より某サイトのリプレイスに取り掛かっております。
リプレイスにあたりバックエンドの技術選定に関しては開発部全員で話し合いました。
案として挙がってきたのは、`【PHPでLaravel】`、`【RubyでRuby on Rails】`、`【GoでClean Architecture】`あたりです。
そもそも、既存のサイトは`【PHPのsymfony】`で作成されており、移行のことを考えるとPHPは有力とされておりましたが最終的な投票の結果、なんと`【GoでClean Architecture】`になりました。
しかも次点は、`【Ruby on Rails】`という、、、PHPには、苦い思い出があったのでしょうか。（想像はできますが）

その選定方法や、選定基準は今回割愛しますが、いずれ発表したいと思います。


さて、その`【GoでClean Architecture】`に決定になったものの`GO言語`の有識者が、当時一人しかいない中での選択で、私自身「びつくりした」というほかありません。
私も趣味程度しか触ったことがなく、`GO言語`を実際に使う機会があるなんて、夢にも思いませんでした。
ただ、そのたった一人の`GO言語`の有識者は2ヶ月ほどでリプレイスメンバーから離れてしまい、内心「大丈夫なのか！？」と思った次第です。

しかーーーーし、そんなことはなんのその、クリーンアーキテクチャの思想を取り入み、TDDを駆使しながらメンバーは着実に力をメキメキとつけていき、わたしは置いてけぼりをくらっております。
しかもしかもしかも、そのリプレイスによって「テストカバレッジ80％を達成する」という目標を抱いておりますので、なんとしても達成していきたいと思っています。

そんなメンバーに置いてけぼりをくらわない＆少しでも力になれるように、ここに備忘録として「`Go`でテストカバレッジ見える化」についてを記載します。

[参考] Go 1.13 

バックエンドはAPIサーバーとして想定しているので、そのテストパッケージもついでにご紹介。

## net/http/httptest パッケージ

`net/http/httptest`はHTTPに関するテストをする際に便利なパッケージです。
`net/http/httptest`パッケージを使用すると、特定のアドレスとポートを`Listen`させることなくWEBアプリケーションのテストができます。

### 簡単なAPIサーバーの例
![Web 1920 – 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42432/d4594fd7-7441-df1a-a1eb-9eb210f20e32.png)
`/contact?name=gopher`と問い合わせすると、`Hello, gopher`と返ってくるアプリケーションです。

これをテストする場合、`go run`して立ち上げ、`curl http://localhost:8080/contact?name=gopher`を実行したり、postman で叩いてテストすることも可能ですが、TCPポート`:8080`がすでに利用されていたり、毎回毎回postmanで叩くのはめんどうですよね。

そんな場合は、`net/http/httptest`を使うと便利です。
ローカルのループバックデバイスで動作するテスト用のサーバを立てることができます。

#### API サーバの例
```go
// server.go
package main

import (
	"fmt"
	"log"
	"net/http"
)

// Route はこのAPIサーバのルーティング設定をしている
func Route() *http.ServeMux {
	m := http.NewServeMux()

	m.HandleFunc("/contact", func(w http.ResponseWriter, r *http.Request) {
		if err := r.ParseForm(); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
		}
		fmt.Fprintf(w, "Hello, %s", r.FormValue("name"))
	})

	return m
}

func main() {
	m := Route()
	log.Fatal(http.ListenAndServe(":8080", m))
}
```

`httptest.Server` が便利なのは、`http.Handler`インターフェイスを引数に渡せることです。
今回だと、`func Route`が `*http.ServeMux` を返すようにしました。
`*http.ServeMux`は `http.handler`のインターフェイスを満たしているため、そのまま`httptest.NewServer` の引数として渡すことができます。


#### テスト用サーバーを利用したテスト
```go
package main

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestRoute(t *testing.T) {
	// テスト用サーバーの立ち上げ
	ts := httptest.NewServer(Route())
	defer ts.Close()

	// テスト用サーバーのURLは、ts.URL で取得できる
	res, err := http.Get(ts.URL + "/contact?name=gopher")
	if err != nil {
		t.Fatalf("http.Get faild: %s", err)
	}
	contact, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		t.Fatalf("read from HTTP Response Body failed: %s", err)
	}
	expected := "Hello, gopher"
	if string(contact) != expected {
		t.Fatalf("response of /contact?name=gopher returns %s, want %s", string(contact), expected)
	}
}
```
`httptest.Server`は`Go`のインターフェイスをうまく利用した使いやすい仕組みであり、テストのための便利なツールセットとなっています。

## テストカバレッジ

`Go`におけるテストカバレッジ計測ツールについてご紹介。
テストカバレッジ見える化をするためには、まず大元であるテストカバレッジ計測ツールや方法が必要ですよね。

### カバレッジ取得方法

```go
go test -cover
```
この際、カバレッジを測定するだけでなく、テストも同時に実行されます。
<img width="337" alt="スクリーンショット 2019-12-10 2.49.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42432/3909190e-e5cd-cb16-b023-ec5cfe935b77.png">

### より詳しい結果の閲覧方法

#### 「go test」で作成されたカバレッジプロファイル

```go
go test -coverprofile=c.out
```

<img width="412" alt="スクリーンショット 2019-12-10 3.06.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42432/c49fdc6c-bc9e-6a56-5252-5c3a44761f68.png">


#### 注釈付きソースコードを表示するWebブラウザーで確認する場合

```go
go tool cover -html=c.out
```

<img width="777" alt="スクリーンショット 2019-12-10 3.06.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42432/1fac2e5e-e40b-1483-0d7d-e3bb7a9fc2c5.png">


#### Webブラウザーを起動する代わりにHTMLファイルを書き出し

```go
go tool cover -html=c.out -o coverage.html
```
※Webブラウザーと同じ

#### 各機能のカバレッジ率を標準出力に表示

```go
go tool cover -func=c.out
```

<img width="408" alt="スクリーンショット 2019-12-10 3.09.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42432/1f92cccb-3016-ca57-b4fa-1835db42c1ed.png">


#### 最後にカバレッジ注釈付きの変更されたソースコードを生成するには

```go
go tool cover -mode=set -var=CoverageVariableName main.go
```

<img width="300" alt="スクリーンショット 2019-12-10 3.10.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42432/d2cdc54c-a753-399f-821e-27c50d608359.png">

テストカバレッジを得ることで、自身のライブラリのテスト計画を練るだけでなく、そのライブラリのテストの指針についても知ることができます。
よくテストされている箇所とテストのあまりされていない箇所を知ることは、テストの傾向を知ることになり、それは実装の指針を知ることにもなります。
テストのカバレッジによってテストの傾向を知り、アプリケーションで起き得るリスクを察知するために、活用できるかと。

### テストカバレッジ見える化＆CI

やっぱり、チームで運用・保守をやるなら、数字や見える化があった方がモチベーションもあがると思いますので、[codecov](https://codecov.io)を使って、カバレッジの見える化も対応したいですね。今回は、[travis-ci](https://travis-ci.org)で試してみました。
やりかたは、[codecov](https://github.com/codecov/example-go)を参考に。

```yml
# .travis.yml
language: go

go:
  - 1.13.x
  - tip

before_install:
  - go get -t -v ./...

script:
  - go test -race -coverprofile=coverage.txt -covermode=atomic

after_success:
  - bash <(curl -s https://codecov.io/bash)
```

<img width="1151" alt="スクリーンショット 2019-12-11 3.04.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42432/77a6e6c4-e974-bcae-74e1-51dac95225e8.png">
<img width="1135" alt="スクリーンショット 2019-12-11 3.06.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42432/9eff69d4-8fa2-7f58-fe23-3b6f97ff8480.png">
<img width="734" alt="スクリーンショット 2019-12-12 20.47.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42432/f61d4693-da0a-56f6-fdf2-51e9acf27222.png">

緑がテストを回している箇所で、赤の部分がテストコードを書いていない部分です。
このように、図や、テスト箇所がすぐに見れるようになると、とてもわかりやすいです。


https://travis-ci.org/andmorefine/go-coveralls
https://codecov.io/gh/andmorefine/go-coveralls
[ソースコード](https://github.com/andmorefine/go-coveralls)


きちんと、テストカバレッジを見える化することができました。

カバレッジをうまく使って、ガシガシ、テストの戦略を練っていきましょう！！！！


[参考文献]
[golang.org/pkg/testing](https://golang.org/pkg/testing/)
[golang.org/pkg/net/http](https://golang.org/pkg/net/http/)
[golang.org/cmd/go](https://golang.org/cmd/go/)
[Go でコードカバレッジを取得する](https://qiita.com/kkohtaka/items/965fe08821cda8c9da8a)
[Golangのtestify/assert 使えそうな関数まとめ](https://qiita.com/JpnLavender/items/21b4574a7513472903ea)
[godoc.org/github.com/stretchr/testify/assert](https://godoc.org/github.com/stretchr/testify/assert)
[codecov/example-go](https://github.com/codecov/example-go)

## お知らせ
エイチームグループでは一緒に活躍してくれる優秀な人材を募集中です。
興味のある方はぜひとも[エイチームグループ採用ページ](https://www.a-tm.co.jp/recruit/)よりご応募ください！
（[Webエンジニア詳細ページ](https://www.a-tm.co.jp/recruit/requirements/career/lifestylesupport-webengineer/)）よりお問い合わせ下さい。


## 明日

明日のエイチーム引越し侍、エイチーム コネクトアドベントカレンダーの担当は、@futa_326さんです！

是非、お楽しみに！

ほなまた！