はじめに
CYBIRDエンジニア Advent Calendar 4日目担当の@ntrvです。
3日目は@keitarouのコードレビューで意識していること(する時/される時)でした。
コーディング規約やデッドコードなどの, あまり本質的ではない部分についてはlinterで解決したいですねー。
golang 5つのTips
- golangを操る上で知らなければいけない!!というわけではありませんが, 知っておくと便利な5つのTipsをご紹介いたします。
- Golang周りに詳しい方には退屈な記事になります。。
- 外から変数を取り込む
- 複数パッケージからcoverprofileを生成する
- どの場所がカバーされているかを手軽に確認する
- 複数のcoverprofileを簡単に1つにまとめる
- クロスコンパイルしバイナリをパッケージングする
1. 外から変数を取り込む
- 以下のようなソースコードが存在したとします。
- もし
compileDate
の中身がある場合はそのまま表示し, 中身がない場合は実行ファイル自身の変更日時をPrint()
から出力します。
- もし
compile_date.go
package cli
import (
"time"
"os"
"fmt"
)
var (
// CompileDate ... Injected compiled date,
compileDate string
)
// pseudoCompileDate ... Return modification time itself.
// If error has occurred, return current date
func pseudoCompileDate() time.Time {
info, err := os.Stat(os.Args[0])
if err != nil {
return time.Now()
}
return info.ModTime()
}
// Print ... Output CompileDate to Stdout
// default is created date itself.
func Print() {
if compileDate != "" {
fmt.Printf("CompileDate: %v\n", compileDate)
} else {
fmt.Printf("CompileDate: %v\n", pseudoCompileDate())
}
}
- 以下のようにビルド日時を
- このとき
date
の実行結果から得られた文字列がcompileDate
変数に入ります。 - ここで
github.com/ntrv/hogehoge
はこのソースの置いてあるリポジトリになります。
- このとき
go build . -ldflags "-X \"github.com/ntrv/hogehoge/cli.compileDate=$(date)\"
ユースケース
- バイナリのビルド日時や, ビルドした際のgitのリビジョンを埋め込むことができます。
2. 複数のパッケージからcoverprofileを生成する
-
go test
の結果をcoverprofileに出力し, 色々することを考えます。 - しかし複数パッケージのcoverprofileは一度に直接生成することは出来ません。
$ go test -v ./... -cover -coverprofile=ntrv.coverprofile
cannot use test profile flag with multiple packages
- そこでパッケージの数だけ
go test
を回すことを考えます。- Makefile内で動的にタスクを追加する。
- bashの
for
で実行してもいいのですが, あるパッケージ単体だけでもテストできるようにします。
方法
- 第一引数
$(1)
を受け取り,test_$(1)
というタスクを作ります。 - ここでは
$(1)
はパッケージ名を想定しております。- coverprofileという名前のディレクトリにプロファイルを吐き出すようにしております。
Makefile
define test_template
.PHONY:test_$(1)
test_$(1): deps
go test -v -race -cover -covermode=atomic -coverprofile=coverprofile/$(1).coverprofile ./$(1)/...
endef
- 以下のようにテンプレートを呼び出すことで
test_foo
,test_bar
,test_buz
といったタスクが生成されます。- foreachの第一引数にindex, 第二引数にリストを指定しtest_templateにパッケージ名を与えます。
Makefile
NOVENDOR_PKG = foo bar buz # 本来はシェルコマンドを使用し動的にパッケージ名のリストを得るようにする
$(foreach pkg, $(NOVENDOR_PKG), $(eval $(call test_template, $(pkg))))
- さらに生成されたタスク
test_*
を実行するためのタスクtest
を作成します。
Makefile
TEST_TARGET = $(foreach target, $(NOVENDOR_PKG), test_$(target)) # => test_foo, test_bar, test_buz
.PHONY:test
test: $(TEST_TARGET)
- これでパッケージの数だけcoverprofileが生成できるようになり, さらに必要に応じて個別のパッケージに対しても実行可能になります。
ユースケース
参考サイト
3. どの行がカバーされているかを簡単に確認する。
- いちいち, githubにプッシュせずともローカルでカバレッジの変化やカバーされている様子を確認したい時があります。そんなときに使用できます。
コマンドライン上から確認する
- 慣れ親しんだコマンドライン上から簡単に確認できます。
$ go tool cover -func=coverprofile/fizzbuzz.coverprofile
github.com/ntrv/hoge/fizzbuzz/call.go:6: Call 100.0%
github.com/ntrv/hoge/fizzbuzz/condition.go:4: IsFizz 100.0%
github.com/ntrv/hoge/fizzbuzz/condition.go:9: IsBuzz 100.0%
github.com/ntrv/hoge/fizzbuzz/condition.go:15: IsFizzBuzz 100.0%
github.com/ntrv/hoge/fizzbuzz/fizzbuzz.go:13: New 100.0%
github.com/ntrv/hoge/fizzbuzz/print.go:10: FprintFizzBuzz 100.0%
github.com/ntrv/hoge/fizzbuzz/print.go:17: PrintFizzBuzz 100.0%
github.com/ntrv/hoge/fizzbuzz/range.go:4: SetRange 100.0%
github.com/ntrv/hoge/fizzbuzz/word.go:4: GetFizz 100.0%
github.com/ntrv/hoge/fizzbuzz/word.go:9: GetBuzz 100.0%
github.com/ntrv/hoge/fizzbuzz/word.go:14: GetFizzBuzz 100.0%
total: (statements) 100.0%
ブラウザ上から確認する
- ブラウザ上から確認すると, どこがカバーされているか一目瞭然です。
$ go tool cover -html=coverprofile/fizzbuzz.coverprofile
4. プロファイルを結合する
- coverprofileがパッケージごとにバラバラに生成されているので, これを1つにまとめCodeCovやCoverallsに送信し全体のカバレッジが見れるようにします。
- コマンドで一つにまとめてもよかったのですが, 面倒だと思ったので以下のようなツールを使用しました。
# coverprofileフォルダ内のプロファイルを一つにまとめる
gover . coverprofile/*.coverprofile
5. クロスコンパイルし, バイナリをパッケージングする
- ネット上にはgoxを使用している例が多いのですがREADMEとまとめてzipやtarでパッケージングしたかったので, goxcを使用しています。
- 日本語だとなかなか資料が少なくオプションが多く複雑なので, goxcの使い方を簡単に紹介いたします。
# -build-ldflags: コンパイラにldflagsオプションを渡します。
# -d: 生成物の入るフォルダを指定します。
# -n: バイナリ名をここで指定します。
goxc -build-ldflags=$(LDFLAGS) -d=bin -n=${BINNAME}
- また設定ファイルは以下のように使用しております。
- またTaskSettingsについては
goxc -help archive
等で確認可能
- またTaskSettingsについては
.goxc.json
{
"ConfigVersion": "0.9", # この設定ファイルのバージョンを指定
"PackageVersion": "0.0.1", # パッケージ名に使用できる
"PrereleaseInfo": "snapshot", # パッケージ名に使用できる
"Tasks": [
"xc",
"copy-resources",
"archive",
"rmbin"
],
"Os": "linux darwin windows", # 対応するOSを列挙
"Arch": "amd64", # 対応するアーキテクチャを列挙
"Resources": {
"Include": "README*,LICENSE*,INSTALL*", # 成果物の中に含むようにする
"Exclude": "*.go" # 成果物の中に含まない
},
"BuildSettings": {
"LdFlagsXVars": {
"version": "indev" # ここでもldflags "-X "相当の設定が可能
}
},
"TaskSettings": {
"xc": {
"autoRebuildToolchain": false,
"validateToolchain": false,
"verifyExe": false
},
"archive-tar-gz": {
"include-top-level-dir": "!windows", # ディレクトリごとまとめてしまうかどうか
"platforms": "linux,darwin" # タスクを実施するプラットフォームを指定
},
"archive-zip": {
"include-top-level-dir": "!windows",
"platforms": "!linux,!darwin"
}
}
}
最後に
- 以上となります!自分自身記事を書くことで, golangの周辺技術を学び直すことができました!
- Goはツールセットが標準整備("Battery Included")されているのがいいところですよね!
- CYBIRD エンジニア Advent Calendar(http://qiita.com/advent-calendar/2017/cybird) 明日は、@pakcheeさんの「プログラマーはなぜ脆弱性対策をしなければならないのか」です!楽しみですね!