GitHub + CircleCIで継続的インテグレーション
前回の記事で、golangのユニットテスト方法について述べた。現在ではコードを書くとユニットテスト、ビルド、統合テスト、デプロイなどを自動的に実行するCI(継続的インテグレーション)が重視されている。CIツールにはJenkinsが有名であり、CIサービスとしてはTravisCIが有名だろうか。ここでは無料で始めることができるCircleCIというサービスを利用する方法を述べる。ソースコード管理も無料で始めることができるGitHubを使うものとする。
GitHubに公開リポジトリを作るところまではググってください。今回は前回の記事のサンプルコードのfoo.goとfoo_test.goを引き続き使う。これらをGitHubの新規リポジトリにpushしておく。次はCircleCIでSign upする。GitHubアカウントでOAuthするのでこちらで新規にアカウントを取得する必要は無い。GitHubアカウントでログインするとリポジトリ選択画面が出るので選択すると即座にCIが実行され始める。以降、GitHubにpushするとそれがトリガーになりCIが走る。結果は次のように成功/失敗がダッシュボードに表示される。
CircleCIでは実行リソースをコンテナという単位で割り当てており、無料では1コンテナまで使用可能。実行性能を引き上げたければ有料で追加コンテナを購入する必要がある。
先ほどfoo.goとfoo_test.goのみをcommit&pushした状態に対してCIが実行されると結果はFailedとなる。CIの内容はブランチトップ階層にcircle.ymlというファイルを置くと指示できる。無ければデフォルトの定義で実行するようだが基本的には失敗するので、circle.ymlを自分で定義する必要がある。
circle.ymlのサンプル
machine: timezone: Asia/Tokyo checkout: override: - git clone https://github.com/MatsuGitHub/MpegTsAnalyzer.git test: override: - cd MpegTsAnalyzer - go test -bench . -benchmem
machineとかdependenciesの項目に公式サイトの説明によると次のようになっている。
machine: adjusting the VM to your preferences and requirements
checkout: checking out and cloning your git repo
dependencies: setting up your project’s language-specific dependencies
database: preparing the databases for your tests
test: running your tests
deployment: deploying your code to your web servers
machineの定義は独特だがcheckout~deploymentは単にコマンドを羅列するだけで、checkoutからdeploymentに向かって順に実行されていくだけに見える。どのコマンドをどこに記述するかは迷うこともあるかもしれない。
overrideはデフォルト定義を上書きする。他にpreやpostという定義ができ、preはoverrideの処理の前に、postはoverrideの処理後に実行される。それ以下にはコマンドを羅列する。今回はgit cloneでソースを引っ張ってきてgo testコマンドを実行するだけとした。CircleCIはJenkinsなどと同じく、過程のどこかで戻り値に0以外の結果が返ってくると失敗とみなす。
golangのユニットテスト
前回の記事では、Visual Studio CodeでのGUIデバッグ環境を構築した。今回はユニットテストとCI(継続的インテグレーション)について述べる。
早速開発を始めたいところだが最近の開発ではユニットテストが重要視される。一人で開発するにしてもテストファーストで設計をしてからコーディングするのは重要だと思う。何となくで書いていくと抽象度が酷いことになるので。
golangには標準でユニットテスト機能が備わっている。go testコマンドだ。Visual Studio Codeと連携まではできないようなのでコマンドで実行しよう。テスト用の関数名やファイル名には規則がある。試しに足し算のみの簡単なサンプルを書いてみる。
foo.go
package foo func Sum(i, j int) int { return i + j }
foo_test.go
package foo import ( "testing" ) func TestSum(t *testing.T) { actual := Sum(10, 20) expected := 30 if actual != expected { t.Errorf("got %v\nwant %v", actual, expected) } } func TestSum2(t *testing.T) { actual := Sum(1, 2) expected := 2 if actual != expected { t.Errorf("got %v\nwant %v", actual, expected) } } func BenchmarkSum(b *testing.B) { for i := 0; i < b.N; i++ { Sum(1, 1) } }
実装はfoo.goに記述し、テストケースはfoo_test.goに記述した。テストケースは実装ファイルxxx.goに対してxxx_test.goとする命名規則にすることになっている。テストケースではtestingパッケージをimportする必要がある。テストケースはTest~というprefixを付けることでgo testコマンドはテストケース関数を判別していると思われる。この2つのファイルがあるディレクトリで
go test ./...
と実行するとテストが走る。TestSum2はわざと失敗するように書いてある。失敗するテストがあるとどういう出力になるかも見てほしい。
他にBenchmark~のprefixのテストケースも書いている。これは-benchフラグを付けると実行に要した時間などを計測してくれる。
go test -bench ./...
for文でb.N回まで繰り返すのは結果の揺らぎを抑えるために複数回実行させるため。b.N回が実際に何回になるかは計測結果の分布に依りgolang処理系が自動的に決定する。揺らぎが閾値を下回った時点で停止するのだろう。
より詳細にメモリのアロケーション回数まで調べることもできる。これは-benchmemフラグにより切り替える。
go test -bench ./... -benchmem
golangはやや低レベルの言語なのでメモリアロケーションを抑制するチューニングの余地は大きい。ヒープ周りの動作仕様を確認する上でも有効なので積極的に活用していきたい。
開発環境構築
HWはMacBook Pro。2016/04/04現在ではMac OS X El Capitan。
パッケージマネージャはHomebrew。Homebrewのインストールまでは情報が多いので他サイトに任せます。El Capitanにアップデート時にHomebrewの動作に影響があるので注意。私はbrew updateができなくなり、ファイルアクセス権の変更が必要でした。
brew update
brew install go
go version
私はbrew updateを怠りすぎてgoのバージョン1.4が入ってしまい、デバッグ環境がビルドできない原因に気付くのにエラい時間がかかりました。brew updateしようとするとEl Capitanのせいで失敗しここでも躓きました。goの現在最新ver.は1.6です。
エディタはvimやSublimeなどのテキストエディタでもいいんですが、デバッグを考えるとIDEを使いたいところです。コマンドラインデバッガもありますが、IDEに慣れた世代としてはマウスオーバーで変数値くらい覗けないと発狂してしまいます。WEB関係のプログラマさんはprint文デバッグのみって人もよく見かけますが流石に効率が違うのでデバッガは活用しましょう。
IDEとしてはLiteIDE、IntelliJ IDEA + Goプラグイン、Visual Studio Codeなどがあります。デバッガとしてはお馴染みGDBかDelveというものがあるようです。強い根拠はありませんがVisual Studio Code + Delveが私にとってベストだと感じました。Windows上でのVisual Studioに慣れているため少し贔屓目があるかもしれません。
Visual Studio Codeのインストール自体は簡単です。ダウンロードしたものをアプリケーションフォルダに放り込みましょう。
https://www.visualstudio.com/ja-jp/products/code-vs.aspx
Visual Studio Codeでgolangを扱うにはExtentionを追加します。Command + Shift + pでコマンドパレットを表示したらExtentionと入力してください。"Extentions: Install Extention"と入力候補が下に出るので選択します。"ext install"と表示されるので"ext install go"と末尾にgoを追加してEnterを押すと追加できます。
次に諸々の設定をしますがその前にgolangのディレクトリについて説明しておく必要があります。golangには2つの重要な環境変数を指定する必要があります。
- GOROOT
- GOPATH
GOROOTはgolangがインストールされたディレクトリを指定します。GOPATHは各ユーザーのワークディレクトリになります。他の言語では理解し難いことですが、golangは開発に使うディレクトリを一つに限定することを想定しています。GOPATH以下に各プロジェクトディレクトリを作って管理します。奇妙な感じですがディレクトリには慣習的なルールがあるためまずは従ってみましょう。
Homebrewでインストールした場合は、GOROOTのパスは上記のものに決まります。GOPATHは自由ですが$HOME以下に適当なディレクトリを一段掘るのが無難でしょう。私はgolangフォルダとしました。$GOPATH/binも慣習的なルールですがビルドしたものはbinフォルダに出力するパッケージが多いのでパスを通しておきます。
GOPATHには慣習的に直下に次の3つのディレクトリが使われます。
- src
- pkg
- bin
Subversionのtrunk/branches/tagsみたいなもんですね。ディレクトリについては下記の説明が一番参考になりました。
自分のコードを書き始めるなら$GOPATH/src/myappとかに書くようにしましょう。
Visual Sudio Codeの話に戻りますが、$GOPATHにいくつかのパッケージがあることを要求します。RubyのgemやPythonのpip、perlのCPANなど最近?の言語はパッケージマネージャを備えることが多くなりました。golangも例外ではなくgoコマンドのgetオプションによりコマンド一発で依存関係を解消しつつサードパーティのパッケージを追加できます。次のパッケージをインストールします。
go get -u -v github.com/nsf/gocode go get -u -v github.com/rogpeppe/godef go get -u -v github.com/golang/lint/golint go get -u -v github.com/lukehoban/go-find-references go get -u -v github.com/lukehoban/go-outline go get -u -v sourcegraph.com/sqs/goreturns go get -u -v golang.org/x/tools/cmd/gorename go get -u -v github.com/tpng/gopkgs go get -u -v github.com/newhook/go-symbols
-uはインストール済みのパッケージが依存関係でアップデートが必要な場合はアップデートすることを示します。-vは依存関係の表示オプションなので無くても動作に支障はないでしょう。
それにしても環境作りが面倒です。Visual StudioやXCodeのインストーラーの素晴らしさを実感するところではありますが、更に面倒なことにデバッガのビルドが必要です。デバッガにはDelveを使うと書きました。ビルド方法はGitHubのページから辿れますがMac OSの場合は下記のページに記載があります。
https://github.com/derekparker/delve/blob/master/Documentation/installation/osx/install.md
自己証明書が必要なのが面倒ですが、そこは説明通りでさほど迷いませんでした。しかし
Clone this project:
git clone git@github.com:derekparker/delve.git && cd delve
と書いてある手順はこれでは上手くいきませんでした。依存パッケージがあるようで
とするとdlvコマンドが使えるようになりました。これでVisual Studio CodeでHello Worldでも書いてブレークポイントを張ってみてください。F5キーでデバッグ実行でき、ブレークポイントで停まるはずです。