Go(Golang)+Herokuで天気予報通知LINE BOTを作成する

Go 言語で 毎日定時に天気予報を通知する LINE BOT を作ってみました。
毎朝6時に天気情報を通知してくれるBOTです。

![スクリーンショット 2021-04-11 8.14.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/297c638f-543e-79f9-c24d-e9d7a93675ea.png)

出来上がった BOT は実生活に役立つと思うのでぜひご覧いただければ幸いです!

なお、今回の実装したアプリは下記で公開しています。

[GitHub - yuki0920/weather_line_bot_sample](https://github.com/yuki0920/weather_line_bot_sample)

## 本記事で扱う内容

- Go Modules による依存管理
- package の分割
- Web API(JSON 形式)へのリクエストとレスポンスのパース
- Heroku への Go アプリケーションのデプロイ
- Heroku での定期タスク実行
- testing パッケージによるテスト

## LINE Developers の設定

以下の手順に沿って LINE BOT 開発の準備を進めましょう。

### チャネルの作成

参考: [LINE Developers コンソールでチャネルを作成する | LINE Developers](https://developers.line.biz/ja/docs/messaging-api/getting-started/#using-console)

チャネル作成は プロダクト -> Messaging API から行います。

![スクリーンショット 2021-04-08 8.23.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/427a240c-6588-090b-0e4e-9c3a12f9c6e2.png)


チャネルの作成の、入力時の画面です。

![developers.line.biz_console_channel_new_type=messaging-api&provider=1651355238 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/330893a6-4625-991e-5966-ee39467185a1.png)

### Messaging API の準備

#### チャネルシークレットの取得

作成したアプリの チャネル基本設定 から、チャネルシークレットを控えておきます。
チャネルシークレットは、 のちほど `LINE_BOT_CHANNEL_SECRET`として、アプリケーションで参照します。

![スクリーンショット 2021-04-08 9.08.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/8190f495-6ff8-952d-4c50-08b421fbc92e.png)

#### チャネルアクセストークンの取得

次に、作成したアプリの Messaging API 設定 から、 チャネルアクセストークン の 発行 ボタンを押し、チャネルアクセストークンを取得します。

チャネルアクセストークンは、 のちほど `LINE_BOT_CHANNEL_TOKEN`として、アプリケーションで参照します。
![スクリーンショット 2021-04-08 9.11.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/a171fc14-57a9-b835-6081-0a905bf10678.png)

![スクリーンショット 2021-04-08 9.11.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/c1c3f9c4-71ba-369b-621a-d1a4d08c0a3a.png)

#### 友達登録

Messaging API 設定 の QR コードから、友達登録しておきましょう。
動作確認時に、正常にメッセージを送れているいるかを確認する際に利用します。
※下記の QR コードは今回作成した BOT の QR コードなので、登録する必要はありません。

![スクリーンショット 2021-04-09 9.32.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/56b8948a-6973-8464-6410-b5397fe5335c.png)

## GitHub リポジトリ作成

Add .gitignore にチェックを入れ、Go を選択しリポジトリを作成します。

![スクリーンショット 2021-04-09 7.26.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/b50676f2-4a1b-a186-c33e-95df5821eb2f.png)

## Go 実装: `Hello, world!`のメッセージ配信

### ディレクトリ構造

まずはじめに、アプリケーションの最終的なのディレクトリ構造を記載します。

```bash
$ tree
.
├── go.mod
├── go.sum
├── main.go
└── weather
    └── weather.go

1 directory, 4 files
```

### 作業前の準備

「GitHub リポジトリ作成」で作成したリポジトリを `git pull` したあとに、リポジトリに移動しましょう。
(Go 学習者の方なら、ghq を利用して`go get`でリポジトリをダウンロードする方も多いかとは思いますが。)

```bash

$ git pull git@github.com:yuki0920/weather_line_bot_sample.git
$ pwd
/Users/yuki.watanabe/ghq/github.com/yuki0920/weather_line_bot_sample
```

### go.mod の作成/依存パッケージのインストール

作成したリポジトリに移動し、Go Modules を利用して依存パッケージを管理できるようにします。

Go の慣習に則って、 `go mod init github.com/<ユーザー名>/<リポジトリ名>` で初期化します。
次に、利用する SDK である `line-bot-sdk-go` をインストールします。

```bash
$ go mod init github.com/yuki0920/weather_line_bot_sample
go: creating new go.mod: module github.com/yuki0920/weather_line_bot_sample
go: to add module requirements and sums:
	go mod tidy

$ go get github.com/line/line-bot-sdk-go/linebot
go get: added github.com/line/line-bot-sdk-go v7.8.0+incompatible
```

初期化時に生成された `go.mod`に、先程インストールしたパッケージのバージョンが記載されています。

```go.mod
module github.com/yuki0920/weather_line_bot_sample

go 1.16

require github.com/line/line-bot-sdk-go v7.8.0+incompatible // indirect
```

Commit: [Add go.mod & go.sum](https://github.com/yuki0920/weather_line_bot_sample/commit/8afe4a2880cdd32c5dcee685c664537654314140) 👈 各ステップごとの Commit のリンクです
参考: [Go 言語の依存パッケージ管理ツール Modules の使い方 | MMM 公式ブログ](https://blog.mmmcorp.co.jp/blog/2019/10/10/go-mod/)

### LINE BOT でメッセージを配信する

下記のように `main.go` を作成してください。

```main.go
func main() {
	// LINE Botクライアント生成する
	// BOT にはチャネルシークレットとチャネルトークンを環境変数から読み込み引数に渡す
	bot, err := linebot.New(
		os.Getenv("LINE_BOT_CHANNEL_SECRET"),
		os.Getenv("LINE_BOT_CHANNEL_TOKEN"),
	)
	// エラーに値があればログに出力し終了する
	if err != nil {
		log.Fatal(err)
	}
	// weatherパッケージパッケージから天気情報の文字列をを取得する
	result, err := weather.GetWeather()
	// エラーに値があればログに出力し終了する
	if err != nil {
		log.Fatal(err)
	}
	// テキストメッセージを生成する
	message := linebot.NewTextMessage(result)
	// テキストメッセージを友達登録しているユーザー全員に配信する
	if _, err := bot.BroadcastMessage(message).Do(); err != nil {
		log.Fatal(err)
	}
}
```

Commit: [Add initial message ](https://github.com/yuki0920/weather_line_bot_sample/commit/d11d69a9426406b08cd8d0bf5cb73649bf5a2e00)
参考: [Messaging API リファレンス | LINE Developers](https://developers.line.biz/ja/reference/messaging-api/#send-broadcast-message)

### Heroku アプリケーションの作成

ここからいくつかの Heroku の操作は CUI と GUI の 2 パターンで解説します。

**CUI**

[Heroku CLI](https://devcenter.heroku.com/ja/articles/heroku-cli)を利用します。

`heroku create <アプリ名>`で Heroku アプリケーションを作成します。

```bash
$ heroku create go-weather-line-bot-sample
```

参考: [CLI からのアプリの作成 | Heroku Dev Center](https://devcenter.heroku.com/ja/articles/creating-apps)

**GUI**

アプリ作成の、入力時のサンプルです。
私の場合アプリ名は `go-weather-line-bot-sample`としました。

![スクリーンショット 2021-04-08 8.59.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/53f172ec-dfc5-2b6d-a245-65ce5cc2472d.png)

参考: [Heroku アプリケーションの作成 単元 | Salesforce Trailhead](https://trailhead.salesforce.com/ja/content/learn/projects/develop-heroku-applications/create-a-heroku-app)

### 環境変数の設定(チャネルシークレット & チャネルアクセストークンの設定)

「Messaging API の準備」で取得した環境変数を設定します。

**CUI**

`heroku config:set <環境変数名> <値>`でで環境変数を設定します。

```bash
$ heroku config:set LINE_BOT_CHANNEL_SECRET <チャネルシークレット>
$ heroku config:set LINE_BOT_CHANNEL_TOKEN <チャネルトークン>
```

参考: [設定と環境設定 | Heroku Dev Center](https://devcenter.heroku.com/ja/articles/config-vars#set-a-config-var)

**GUI**

Settings Config Vars より Reveal Vars を押すことで設定できます。
まず、KEY に `LINE_BOT_CHANNEL_SECRET`, VALUE に取得したチャネルシークレットを入力し、ADD します。
次に、`LINE_BOT_CHANNEL_TOKEN`, VALUE に取得したチャネルアクセストークンを入力し、ADD します。
入力時のサンプルです。

![スクリーンショット 2021-04-08 9.21.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/618a2cfc-9bd2-0299-54b3-bcb28b1a12bc.png)

### デプロイする

`heroku git:remote -a <Herokuのアプリ名>` でリモートリポジトリを登録しましょう。

```bash
$ heroku git:remote -a go-weather-line-bot-sample
# リモートリポジトリを確認
$ git remote -v
heroku	https://git.heroku.com/go-weather-line-bot-sample.git (fetch)
heroku	https://git.heroku.com/go-weather-line-bot-sample.git (push)
origin	ssh://git@github.com/yuki0920/weather_line_bot_sample.git (fetch)
origin	ssh://git@github.com/yuki0920/weather_line_bot_sample.git (push)
```

次に、 GitHub への push と Heroku への push をします。
※ GitHub への push は必須ではないですが、念のため。

```bash
$ git push origin main
$ git push heroku main
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 8 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (10/10), 1.99 KiB | 1017.00 KiB/s, done.
Total 10 (delta 1), reused 3 (delta 0), pack-reused 0
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Building on the Heroku-20 stack
remote: -----> Determining which buildpack to use for this app
remote: -----> Go app detected
remote: -----> Fetching jq... done
remote: -----> Fetching stdlib.sh.v8... done
remote: ----->
remote:        Detected go modules via go.mod
remote: ----->
remote:        Detected Module Name: github.com/yuki0920/weather_line_bot_sample
remote: ----->
remote:  !!    The go.mod file for this project does not specify a Go version
remote:  !!
remote:  !!    Defaulting to go1.12.17
remote:  !!
remote:  !!    For more details see: https://devcenter.heroku.com/articles/go-apps-with-modules#build-configuration
remote:  !!
remote: -----> New Go Version, clearing old cache
remote: -----> Installing go1.12.17
remote: -----> Fetching go1.12.17.linux-amd64.tar.gz... done
remote: -----> Determining packages to install
remote:
remote:        Detected the following main packages to install:
remote:        		github.com/yuki0920/weather_line_bot_sample
remote:
remote: -----> Running: go install -v -tags heroku github.com/yuki0920/weather_line_bot_sample
remote: github.com/line/line-bot-sdk-go/linebot
remote: github.com/yuki0920/weather_line_bot_sample
remote:
remote:        Installed the following binaries:
remote:        		./bin/weather_line_bot_sample
remote:
remote:        Created a Procfile with the following entries:
remote:        		web: bin/weather_line_bot_sample
remote:
remote:        If these entries look incomplete or incorrect please create a Procfile with the required entries.
remote:        See https://devcenter.heroku.com/articles/procfile for more details about Procfiles
remote:
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote:
remote: -----> Compressing...
remote:        Done: 3.5M
remote: -----> Launching...
remote:        Released v5
remote:        https://go-weather-line-bot-sample.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/go-weather-line-bot-sample.git
 * [new branch]      main -> main
```

中段に `The go.mod file for this project does not specify a Go version`という文言が出ていますね。
これは、go のバージョンをバージョンを伝達できていないので、次項で解決します。

同じく中段の`Installed the following binaries`に注目です!
push しただけで、実行可能なバイナリファイルが生成されました。

`./bin/weather_line_bot_sample`を実行するだけでアプリケーションが起動する、ということです。
とっても簡単ですね。

### Heroku 用のコメント追加

`go.mod` に Heroku 用のコメントを追加します。
このコメントを追加しないと、Heroku に go のバージョンをバージョンを伝達できません。

```diff
diff --git a/go.mod b/go.mod
index 4e0e5a9..e91563e 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,6 @@
 module github.com/yuki0920/weather_line_bot_sample

+// +heroku goVersion go1.16
 go 1.16

 require github.com/line/line-bot-sdk-go v7.8.0+incompatible // indirect

```

Commit: [Add heroku goVersion comment ](https://github.com/yuki0920/weather_line_bot_sample/commit/12c7fe7b0b1d179b2f6df62369131b3b24a37260)

### Heroku アプリを実行する

それでは、デプロイ時に生成されたバイナリファイルを実行します。

**CLI から**
`heroku run ./bin/<バイナリファイル名>` で実行可能です。

```bash
$ heroku run ./bin/weather_line_bot_sample
Running ./bin/weather_line_bot_sample on ⬢ go-weather-line-bot-sample... up, run.1089 (Free)
```

**コンソールから**

Heroku の画面右上の`More`から`Run console`をクリックしてください。
![スクリーンショット 2021-04-09 9.29.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/11b43679-75ab-28a0-15d3-8c6a302a130b.png)

コンソール画面が起動するので、`./bin/<Herokuのアプリ名>`を入力し、`Run`を押してください。
![スクリーンショット 2021-04-09 9.30.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/7ede0e47-8d1c-bf55-a0b9-64be036f476b.png)

実行後に、コンソール画面にエラーが出力されず、左下に`Process exited`が出力されれば、実行完了です。
![スクリーンショット 2021-04-09 9.30.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/8329c9fa-6133-73d5-1fd2-85ee5d02512d.png)

自分宛てに`hello, world`のメッセージが届いていれば無事成功です。
![スクリーンショット 2021-04-09 9.36.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/432d1883-6299-0bdc-ff87-fc888ba9c05a.png)

## 天気情報の取得と配信をする

LINE による `hello, world` のメッセージ配信ができたので、 `hello, world` をリアルな天気情報に置き換えていきます。

### 使用する API について

気象庁公開の JSON 形式のデータを利用します。
なかでも、東京都の API である https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json にリクエストを送信します。

### 天気情報の取得と標準出力(ターミナル)への出力

```bash
mkdir weather
touch weather/weather.go
cd ./weather
```

下記のように、main パッケージを定義します。実装の説明はコメントを参照ください。

ここでは、`go run` によって動作確認したいため、`pakcage main`としていますが、のちのち`package weather`に変換します。

(よくよく考えると、ここは`weather`ではなく`models`のようなディレクトリ名にしておいたほうが、変更に耐えやすかったかなと思います。)

```weather/weather.go
package weather

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

// レスポンスの形式に合わせて struct を定義する
type Weather struct {
	Area     string `json:"targetArea"`
	HeadLine string `json:"headlineText"`
	Body     string `json:"text"`
}

func GetWeather() (str string, err error) {
	body, err := httpGetBody("https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json")
	if err != nil {
		// エラーを呼び出し元へ委譲する
		return str, err
	}
	weather, err := formatWeather(body)
	if err != nil {
		// エラーを呼び出し元へ委譲する
		return str, err
	}

	result := weather.ToS()

	return result, nil
}

func httpGetBody(url string) ([]byte, error) {
	// HTTPリクエストを発行しレスポンスを取得する
	response, err := http.Get(url)
	if err != nil {
		// エラーをラップして返す
		err = fmt.Errorf("Get Http Error: %s", err)
		return nil, err
	}
	// レスポンスボディを読み込む
	body, err := io.ReadAll(response.Body)
	if err != nil {
		// エラーをラップして返す
		err = fmt.Errorf("IO Read Error:: %s", err)
		return nil, err
	}
	// 読み込み終わったらレスポンスボディを閉じる
	defer response.Body.Close()

	return body, nil
}

func formatWeather(body []byte) (*Weather, error) {
	weather := new(Weather)
	if err := json.Unmarshal(body, weather); err != nil {
		// エラーをラップして返す
		err = fmt.Errorf("JSON Unmarshal error: %s", err)
		return nil, err
	}
	return weather, nil
}

func (w *Weather) ToS() string {
	area := fmt.Sprintf("%sの天気です。\n", w.Area)
	head := fmt.Sprintf("%s\n", w.HeadLine)
	body := fmt.Sprintf("%s\n", w.Body)
	result := area + head + body

	return result
}
```

`go run weather.go`によって main 関数を実行して、下記のように天気情報が出力されれば成功です。

```bash
$ pwd
/Users/yuki.watanabe/ghq/github.com/yuki0920/weather_line_bot_sample/weather
$ go run weather.go
東京都の天気です。
小笠原諸島では、強風に注意してください。伊豆諸島南部、小笠原諸島では、高波に注意してください。東京地方では、乾燥や霜に対する農作物の管理に注意してください。
　本州付近は高気圧に覆われています。一方、伊豆諸島付近には湿った空気が流れ込んでいます。

　東京地方は、晴れとなっています。

　１１日は、高気圧に覆われますが、湿った空気の影響を受けるため、晴れで、夜は曇りとなるでしょう。

　１２日は、高気圧に覆われますが、湿った空気の影響を受けるため、晴れで、夕方から曇りとなる見込みです。

【関東甲信地方】
　関東甲信地方は、伊豆諸島で曇りや雨の所があるほかは、おおむね晴れています。

　１１日は、高気圧に覆われますが、湿った空気の影響を受けるため、晴れや曇りで、朝晩は伊豆諸島で雨の降る所があるでしょう。

　１２日は、高気圧に覆われますが、湿った空気の影響を受けるため、晴れや曇りで、はじめ伊豆諸島では雨の降る所がある見込みです。

　関東地方と伊豆諸島の海上では、１１日から１２日にかけて、うねりを伴い波が高いでしょう。船舶は高波に注意してください。
```

Commit: [Add weather.go ](https://github.com/yuki0920/weather_line_bot_sample/commit/e4936f73b5845a20475490dab01ac86f2ecb3677)

### LINE に天気情報を配信する

これまでに、天気情報を取得して、ターミナルへ出力できるようにしました。
次に、この天気情報を LINE に配信できるようにします。

`weather/weather.go`の下記の点を修正をします。

- `package main` を `package weather` に修正する
- `func main()` を 削除する

diff は下記のようになります。

```diff
$ git diff
diff --git a/weather/weather.go b/weather/weather.go
index 3bb8ca1..9319fd1 100644
--- a/weather/weather.go
+++ b/weather/weather.go
@@ -1,4 +1,4 @@
-package main
+package weather

 import (
        "encoding/json"
@@ -15,11 +15,6 @@ type Weather struct {
        Body     string `json:"text"`
 }

-func main() {
-       result := GetWeather()
-       fmt.Println(result)
-}
-
 func GetWeather() string {
        jsonStr := httpGetStr("https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json")
        weather := formatWeather(jsonStr)
```

Commit: [Rename package & func ](https://github.com/yuki0920/weather_line_bot_sample/commit/068205c7b64ec29c7855d94cacd9b73de5f5b580)

次に、main.go に移動し、下記の点を修正します。

- `weather` パッケージを `import` に追加する
- LINE BOT から配信するテキストメッセージを`hello, world`から、先程追加した天気情報に変更する

diff は下記のようになります。

```diff
diff --git a/main.go b/main.go
index 4cba44c..a881b42 100644
--- a/main.go
+++ b/main.go
@@ -5,6 +5,7 @@ import (
        "os"

        "github.com/line/line-bot-sdk-go/linebot"
+       "github.com/yuki0920/weather_line_bot_sample/weather"
 )

 func main() {
@@ -18,8 +19,10 @@ func main() {
        if err != nil {
                log.Fatal(err)
        }
+       // weatherパッケージパッケージから天気情報の文字列をを取得する
+       result := weather.GetWeather()
        // テキストメッセージを生成する
-       message := linebot.NewTextMessage("hello, world")
+       message := linebot.NewTextMessage(result)
        // テキストメッセージを友達登録しているユーザー全員に配信する
        if _, err := bot.BroadcastMessage(message).Do(); err != nil {
                log.Fatal(err)
```

Commit: [Use weather package ](https://github.com/yuki0920/weather_line_bot_sample/commit/f891af78fc190615cc512c7beadf8fb3af0f34b9)

### デプロイ & 動作確認

それでは、デプロイしましょう。

```bash
# githubへpushする
$ git push origin main
# herokuへpushする(ログは省略)
$ git push heroku main
```

デプロイに成功したら、「Heroku アプリを実行する」の手順と同様に、`./bin/<リポジトリ名>`を実行しましょう。

```bash
$ heroku run ./bin/weather_line_bot_sample
Running ./bin/weather_line_bot_sample on ⬢ go-weather-line-bot-sample... up, run.1089 (Free)
```

無事、LINE Bot よりメッセージが届いていれば成功です!
![スクリーンショット 2021-04-11 8.13.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/c0fce4b5-af0a-4f05-979b-68451d11848d.png)



参考: [GitHub - heroku/heroku-buildpack-go: Heroku Go Buildpack](https://github.com/heroku/heroku-buildpack-go#go-module-specifics)

### 毎日の定期実行を設定する

CLI でスケジューラーのアドオンを追加します。

```bash
$ heroku addons:create scheduler
Creating scheduler on ⬢ go-weather-line-bot-sample... free
 To manage scheduled jobs run:
 heroku addons:open scheduler

Created scheduler-objective-53631
Use heroku addons:docs scheduler to view documentation

$ heroku addons:open scheduler
Opening https://addons-sso.heroku.com/apps/2e75ef40-a75f-4c29-8d05-e9b616aeb800/addons/c54271dd-46c6-4f12-847f-f0cb56daa6a7...
```

ブラウザでスケジューラの設定画面が開くので、
`Add Job`から、 Schedule と Run Command を設定し、 Save Job します。

- Schedule は Every day at(毎日) 9:00 PM UTC(日本時間では 6:00 AM) を入力する
- Run Command は `./bin/<アプリ名>` を入力する

設定した時間に天気予報のメッセージが届けば成功です。

参考: [Heroku Scheduler | Heroku Dev Center](https://devcenter.heroku.com/articles/scheduler#installing-the-add-on)

## 追記: リファクタリング & テスト
記事執筆後、テストコードの書き方について学習し、実装したので記載します。

まず、weather パッケージの `GetWeather`関数からメソッドを分離するリファクタリングを実施します。

```diff
diff --git a/weather/weather.go b/weather/weather.go
index 9319fd1..eae9cf4 100644
--- a/weather/weather.go
+++ b/weather/weather.go
@@ -18,11 +18,7 @@ type Weather struct {
 func GetWeather() string {
        jsonStr := httpGetStr("https://www.jma.go.jp/bosai/forecast/data/overview_forecast/130000.json")
        weather := formatWeather(jsonStr)
-
-       area := fmt.Sprintf("%sの天気です。\n", weather.Area)
-       head := fmt.Sprintf("%s\n", weather.HeadLine)
-       body := fmt.Sprintf("%s\n", weather.Body)
-       result := area + head + body
+       result := weather.ToS()
 
        return result
 }
@@ -50,3 +46,12 @@ func formatWeather(str string) *Weather {
        }
        return weather
 }
+
+func (w *Weather) ToS() string {
+       area := fmt.Sprintf("%sの天気です。\n", w.Area)
+       head := fmt.Sprintf("%s\n", w.HeadLine)
+       body := fmt.Sprintf("%s\n", w.Body)
+       result := area + head + body
+
+       return result
+}
```

Commit: [Extract ToS method ](https://github.com/yuki0920/weather_line_bot_sample/commit/4f859b44c283aa01cf8e5abf2c8d0cf2fa7f70c3)

次に、`weather_test.go`を作成し、下記のように実装します。
testingパッケージのExamplesという機能を利用しています。
Output以降のコメントをテストコードの期待値として定義できます。

```weather/weather_test.go
package weather

import (
	"fmt"
)

func ExampleToS() {
	w := new(Weather)
	w.Area = "テスト地方"
	w.HeadLine = "一日良い天気です。"
	w.Body = "傘を持ち歩く必要はないでしょう。"

	fmt.Println(w.ToS())
	// Output: テスト地方の天気です。
	// 一日良い天気です。
	// 傘を持ち歩く必要はないでしょう。
}
```

Commit: [Add test for ToS ](https://github.com/yuki0920/weather_line_bot_sample/commit/a7baca7e3efe72c5efee9c77c30b4079c30b3fd7)

`go test`でテストを実行すると、OKと出力されていることがわかります。

```bash
$ pwd
/Users/yuki.watanabe/ghq/github.com/yuki0920/weather_line_bot_sample/weather
$ go test
PASS
ok      github.com/yuki0920/weather_line_bot_sample/weather     0.070s
```

失敗時は下記のように、実際の出力値とがgotとして、Outputの値がwantとして出力されます。

```bash
$ go test
--- FAIL: ExampleToS (0.00s)
got:
テスト地方の天気です。
一日良い天気です。
傘
want:
テスト地方の天気です。
一日良い天気です。
傘を持ち歩く必要はないでしょう。
FAIL
exit status 1
FAIL    github.com/yuki0920/weather_line_bot_sample/weather     0.299s
```

## 補足: web プロセス再起動による不定期実行の停止

Heroku にデプロイ後に、Resource の画面に `web bin/<アプリ名>`の設定がありますので、こちらの設定を OFF にしてください。

ON/OFF の見分けをつけづらいので下記画像を参考にしてください。

| ON                                                                                                                                                         | OFF                                                                                                                                                        |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![スクリーンショット 2021-04-09 20.49.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/ad715888-a034-a006-e7ca-1935ef8882c4.png) | ![スクリーンショット 2021-04-09 20.49.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331354/204dd792-00dd-5dd2-b9b5-3deec2eae30c.png) |

この設定は、dyno 起動時にコマンドを設定して`web`プロセスを起動するかどうかの設定です。
dyno が不定期で再起動するため、定期スケジューリングとはことなる予測不可能なタイミングで`bin/<アプリ名>`が実行され LINE に通知が来てしまいます。
この再起動時に実行されるコマンドの無効化をしている、というわけです。

> dyno は永続的なものではなく、アプリとシステム全体の健全性を維持するため 1 日に 1 回以上再起動されます。
> [Heroku Dynos - ライフサイクル | Heroku](https://jp.heroku.com/dynos/lifecycle)

## うまくデプロイできない場合

- 手順については公式ドキュメントとして、[Heroku でサンプルボットを作成する | LINE Developers](https://developers.line.biz/ja/docs/messaging-api/building-sample-bot-with-heroku/) や [line-bot-sdk-java/sample-spring-boot-echo at master · line/line-bot-sdk-java · GitHub](https://github.com/line/line-bot-sdk-java/tree/master/sample-spring-boot-echo#step-2) にも詳しく記載がありますので、ご確認ください
- [設定と環境設定 | Heroku Dev Center](https://devcenter.heroku.com/ja/articles/config-vars#view-current-config-var-values) を参考に LINE で取得したチャネルシークレットとチャネルアクセストークンが、Heroku の環境変数に反映されているかご確認ください
- チャネルを友達登録しているかご確認ください

## さいごに

お読みいただきありがとうございました。
少しでも役に立った、いいね、と思っていただいたら「LGTM」していただけると嬉しいです。