Edited at

golang 知っておくと役に立つ5つのTips

More than 1 year has passed since last update.


はじめに

CYBIRDエンジニア Advent Calendar 4日目担当の@ntrvです。

3日目は@keitarouコードレビューで意識していること(する時/される時)でした。

コーディング規約やデッドコードなどの, あまり本質的ではない部分についてはlinterで解決したいですねー。


golang 5つのTips


  • golangを操る上で知らなければいけない!!というわけではありませんが, 知っておくと便利な5つのTipsをご紹介いたします。


    • Golang周りに詳しい方には退屈な記事になります。。




  1. 外から変数を取り込む

  2. 複数パッケージからcoverprofileを生成する

  3. どの場所がカバーされているかを手軽に確認する

  4. 複数のcoverprofileを簡単に1つにまとめる

  5. クロスコンパイルしバイナリをパッケージングする


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が生成できるようになり, さらに必要に応じて個別のパッケージに対しても実行可能になります。


ユースケース


  • 得られた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等で確認可能




.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")されているのがいいところですよね!