- 投稿日:2021-01-16T23:21:31+09:00
GoをHerokuにデプロイするときに Push rejected, failed to compile Go app. のエラー
エラーについて
Goで作ったAPIをHerokuにデプロイしようと思い、
git push heroku main
コマンドをしたところ! Push rejected, failed to compile Go app. ! Push failed上のようなエラーで失敗。
解決した方法
ログを遡ってみると、
-----> !! The go.mod file for this project does not specify a Go version !! !! Defaulting to go1.12.17 !! !! For more details see: https://devcenter.heroku.com/articles/go-apps-with-modules#build-configuration !! -----> New Go Version, clearing old cacheのような記述があった。最初はこれが原因だとは思っていなかったため放置していたのだが、試しに調べてみることにした。
調べてみると、Herokuでgoのバージョンを指定するには、go.modにて
go.modmodule xxxx // +heroku goVersion go1.15 <--追加 go 1.15のように記述する必要があるようだ。
書き直してpushしなおしてみると、無事デプロイできた。
- 投稿日:2021-01-16T23:18:17+09:00
キーエンス プログラミング コンテスト 2021のメモ
前置き
Atcoderをやってみたので、自分用のメモです。
あとから加筆・修正する予定です。問題
https://atcoder.jp/contests/keyence2021
A
Q_A.gopackage main import ( "bufio" "fmt" "os" "strconv" ) var sc = bufio.NewScanner(os.Stdin) func nextInt() int64 { sc.Scan() i, e := strconv.ParseInt(sc.Text(),10,64) if e != nil { panic(e) } return i } func main() { sc.Split(bufio.ScanWords) var N int fmt.Scanf("%d", &N) var a int64 var c int64 b := make([]int64, N) d := make([]int64, N) a = nextInt() d[0] = a for i:=1; i<N; i++ { a = nextInt() if d[i-1] > a{ d[i] = d[i-1] } else { d[i] = a } } for i:=0; i<N; i++ { b[i] = nextInt() } c = d[0] * b[0] fmt.Printf("%d\n", c) for i:=1; i<N; i++{ if d[i] * b[i] > c{ c = d[i] * b[i] } else { c = c } fmt.Printf("%d\n", c) } }B
Q_B.gopackage main import ( "bufio" "fmt" "sort" "os" "strconv" ) var sc = bufio.NewScanner(os.Stdin) func nextInt() int64 { sc.Scan() i, e := strconv.ParseInt(sc.Text(),10,64) if e != nil { panic(e) } return i } func main() { sc.Split(bufio.ScanWords) var N, K int fmt.Scanf("%d %d", &N, &K) a := make([]int64, N) box := make([]int64, K) for i:=0; i<N; i++{ a[i] = nextInt() } sort.Slice(a, func(i, j int) bool { return a[i] < a[j] }) var j int = 0 var tmp int64 = -1 for i:=0; i<N; i++{ if a[i] == tmp{ if box[j] == a[i]{ box[j] = a[i]+1 } if j+1 < K{ j = j+1 } } else { if box[0] == a[i]{ box[0] = a[i]+1 } tmp = a[i] if 1 < K{ j = 1 } } } var sum int64 for i:=0; i<K; i++{ sum += box[i] } fmt.Printf("%d\n", sum) }C
覚えてたら後で書きます。
D
覚えてたら後で書きます。
E
覚えてたら後で書きます。
F
覚えてたら後で書きます。
- 投稿日:2021-01-16T16:06:18+09:00
オレオレgoimportsを作ってみた
やりたいこと、作ったもの
goimports
最高
goimports
はGoのCLIツールで、*.go
ファイルに対して↓を実行してくれます。
import
していない外部パッケージをimport
してくれる- 使っていない
import
は削除してくれる- 最後に
gofmt
もかけてくれるでも
goimports
は変な空行を入れてくるgithub.com/golang/go/issues/20818 のIssueにある通り、
goimports
は変な空行を入れてきます。
こうなっている理由は単に「仕様が決まっていないから」で、Issueでも4年間くらい議論され続けています。空行を最低限にする
goimports
を作ったこういう状況ならオレオレ
goimports
を自作するしかないだろうと思って作ってみました。https://github.com/rinchsan/gosimports
どれだけ空行を入れていようが順番をバラバラにしていようが、問答無用で空行の数を最低限にしちゃいます。
つまり標準パッケージとそれ以外の間に空行を1つだけ入れます(本家と同じく-local
オプションを使えばプロジェクト内のパッケージの前にも空行を入れることができます)。
import
ブロック内にコメントを書いていた場合も問答無用で削除しちゃいます(これはいい感じの仕様があれば改善したいなと思っています)。作り方
今回作った
gosimports
は本家のgoimports
のコードを改変する形で実装しました。goimportsの処理は大きく分けると3つ
1. importするパッケージを整理する
*.go
ファイルの中身を見て使用しているパッケージを静的解析しています。
本家のソースコードでいうと このあたり ですね。
今回の自作ツールではここはまったく改変していません。2. グループごとに空行を入れる(gosimportsはここを変えた)
gofmt
はimport
ブロック内の空行で区切られたパッケージ群をそれぞれAlphabetical orderにソートします。
なので本家goimports
は出来るだけキレイにするために(?)、標準パッケージとそれ以外の間に空行を入れる処理をgofmt
の前段に行っています。
ソースコードでいうと このあたり ですね。3. gofmtを実行する
最後に
gofmt
をかけて終了です。2番の処理を改変した
今回自作したほうの
gosimports
では、↑の2番の処理を改変しています。
ソースコードでいうと このあたり です。
本家の方にあったaddImportSpaces
関数をseparateImportsIntoGroups
関数に変更しています。
import
のグループ分け(標準パッケージとそれ以外とか)はすでに本家のほうに 分類機能 が実装されていたのでそれを使いまわしました。余談
派生OSSはオリジナルのライセンスを保持する
Goのツール群は多分だいたいBSDライセンスというライセンスのもとでOSSとして公開されていて、
goimports
もそうです。
BSDライセンスで公開されているプロジェクトは、ライセンスをそのまま保持しつつオリジナル著者の名前をプロモーションとかで使ったりすることをしなければ、自由にソースを改変・再配布してもオーケーです。
今回作ったgosimports
でもオリジナルのライセンスをそのまま保持しています。 OSS最高。コード生成などにgofmtは特に便利
今回作った
gosimports
はコード生成をしている部分がかなり適当に作っています。
タブ文字がなかったり無駄な空行とかが入りまくったりしています。
でもその後段で実行しているgofmt
がすべてをキレイにしてくれるので、実装するのがとても楽でした。
普段からgofmt
にはお世話になっていますが、コード生成とかを実装するときには特に便利だなと思いました。Renovateとrelease-drafterがとても便利
gosimports
が依存しているパッケージはgolang.org/x/mod
とgolang.org/x/tools
だけですが、golang.org/x/tools
はgitのタグが切られておらずリリースは最新のコミットハッシュが更新されるだけです。
Renovate はタグのリリースだけではなくコミットハッシュの更新にも対応しているので、ちゃんと最新に追従することができます。
あと、masterブランチにPRがマージされるたびにリリースノートに追記してくれる release-drafter もとても便利でした。gosimportsという命名は割と気に入っている
gosimports = simpler + goimports
です。
結構気に入っています。
- 投稿日:2021-01-16T16:04:42+09:00
DATA-DOG/go-txdbでDB接続を含むテストを楽に書こう
DB接続を含むテストはツライ
- テスト用のデータをテストケースごとに用意しないといけない。
- DBの変更結果が他のテストケースに影響を与えないようにリセットしないといけない。
- DBへの変更が他のテストケースに影響を与えるので並列実行できない。
DATA-DOG/go-txdbを使うと改善できる
- DATA-DOG/go-txdb で生成することのできるDBコネクションには↓のような特徴があります。
sql.DB
と互換性がある。- すべてのクエリが独立したトランザクション内で実行される。
.Close()
を呼ぶとそのトランザクション内で実行されたクエリがすべてRollbackされる。- これをうまく使うと、テストケースごとに独立したトランザクション内でクエリを実行することができ、テスト終了後にDB変更がRollbackされるので、テストケースごとのデータ処理が必要なくなり、他のテストケースへの影響もなくなるのでテストを並列実行することができます。
- 実際のサンプルコードは こちら に置いています。
軽い解説
pkg/dao/dao_test.go
- 今回はDB接続を含むコードを
dao
パッケージに置きます。TestMain
を定義して、テストに必要なデータを go-testfixtures/testfixtures を使って挿入します。
testfixtures
は平行テストに対応していないことがREADMEにも書いてありますが、今回はTestMain
でしか呼ばれないので問題ありません。- 後のテストで
txdb
を使うためにtxdb.Register
を呼んでおきます。func TestMain(m *testing.M) { prepare() txdb.Register("txdb", "mysql", config.DB.DSN) code := m.Run() os.Exit(code) } func prepare() { db, err := sql.Open(config.DB.Driver, config.DB.DSN) if err != nil { panic(err) } defer db.Close() fixtures, err := testfixtures.New( testfixtures.Database(db), testfixtures.Dialect("mysql"), testfixtures.Directory("/go/src/github.com/rinchsan/txdb-todo/testdata/fixtures"), ) if err != nil { panic(err) } if err := fixtures.Load(); err != nil { panic(err) } }
pkg/dao/user_test.go
- ユーザ追加のテストを例に取ります。
- テスト対象のコードやDBのスキーマなどの詳細はGitHubのほうを見てください。
sql.Open
に渡すDriverに"txdb"
を指定して取得したコネクションを使ってテストをしていきます。
sql.Open
の第2引数に渡す文字列ごとに独立したトランザクションを作成してくれます。defer
で呼んでいるdb.Close()
によってテストケースごとにDBの変更がRollbackされています。t.Parallel()
を利用してテストを平行に走らせることも可能です。- ちなみに今回の例では環境変数を利用することで、ローカル開発用に使っているデータベースとは別のものを使ってテストを行うようにしていますので、詳しくは
Makefile
のtest
を見てみてください。func TestUserImpl_Add(t *testing.T) { t.Parallel() cases := map[string]struct { user *entity.User noErr bool }{ "new user": { user: &entity.User{Username: "rinchsan"}, noErr: true, }, "duplicate username": { user: &entity.User{Username: "John"}, noErr: true, }, "empty username": { user: &entity.User{Username: ""}, noErr: true, }, } for name, c := range cases { c := c t.Run(name, func(t *testing.T) { t.Parallel() db, err := sql.Open("txdb", uuid.New().String()) assert.NoError(t, err) defer db.Close() impl := dao.NewUser(db) err = impl.Add(context.Background(), c.user) if c.noErr { assert.NoError(t, err) } else { assert.Error(t, err) } }) } }感想
ビジネスロジックのテストをgomockとかを使って書くと、今回のtxdbと合わせて結構いい感じにプロジェクト全体のテストが書けそうです。
- 投稿日:2021-01-16T09:11:48+09:00
[Mac OS Big Sur]でGOの開発環境を作る
GOを使う必要があったため、Mac OS Big Surで環境を作りました。
内容
- goenvのインストール
- goのインストール
goenvのインストール
当初、
brew install goenv
でインストールしてみたいのですが、
バージョンが古るかったため、Gitでインストールしました。% git clone https://github.com/syndbg/goenv.git ~/.goenvシェルはzshを使っているので、環境変数を設定します。
% echo 'export GOENV_ROOT="$HOME/.goenv"' >> ~/.zshrc % echo 'export PATH="$GOENV_ROOT/bin:$PATH"' >> ~/.zshrc % echo 'eval "$(goenv init -)"' >> ~/.zshrcシェルを再起動し環境変数を反映します。
% exec $SHELLgoenvのバージョンを確認します。
% goenv -v goenv 2.0.0beta11
goのインストール
インストール可能なgoのバージョンを確認します。
% goenv install -l Available versions: 1.2.2 1.3.0 : 1.15.4 1.15.5 1.15.6 1.16beta1バージョンを指定してgoをインストールします。
% goenv install 1.15.6
シェルはzshを使っているので、環境変数を設定します。
% echo 'export PATH="$GOROOT/bin/$PATH"' >> ~/.zshrc % echo 'export PATH="$PATH:$GOPATH/bin"' >> ~/.zshrc % exec $SHELLここまでで、
~/.zshrc
には、以下の設定が追加されているはずです。export GOENV_ROOT="$HOME/.goenv" export PATH="$GOENV_ROOT/bin:$PATH" eval "$(goenv init -)" export PATH="$GOROOT/bin/$PATH" export PATH="$PATH:$GOPATH/bin"使用するGOのバージョンを指定します。
% goenv global 1.15.6GOのバージョンを確認します。
% go version go version go1.15.6 darwin/amd64任意にプロジェクトの場所を指定したいのでGo Modulesを設定します。
まずは、Go Modulesの設定を確認します。% go env GO111MODULE
設定されていれば
on
と表示されますが、表示されないので設定します。% go env -w GO111MODULE=on % go env GO111MODULE onここまででGOの環境ができているので、
go mod init <プロジェクト名>
で進めていきましょう。
- 投稿日:2021-01-16T00:43:55+09:00
Goで現在時刻のテストどうする話(めっちゃ薄いライブラリ作った)
どういう記事?
現在時刻が絡むテストってGolangではどうするんだろうと思い調べた。
しっくりくるのがなかったので欲しい機能だけ入れたライブラリを作った。調べた内容
@tomtwinkle さんの記事:[Golang]テスタブルな現在時刻取得処理を作る では以下3つの方法が紹介されてた。
- monkey.Patch
- flextimeパッケージ
- contextで引き回す
他にはKentaro Kawanoさんの資料 も参考になった。
- clockパッケージ
- 外部からの注入
でも、なんか、こう、もっと簡単でいいだよなー。。。
作ったもの
って思ってめっちゃ薄いライブラリ作ってみた。
https://github.com/bubusuke/xtime考えたこと
flextimeやclockとやろうとしていることは同じ。time.Now()をラップしてて置き換えできる。
でもこのふたつ、自分としてしっくりきてなかった。flextimeは排他制御しててちょっと気になる。(この記事にも記載あり)
あと、flextimeって名前長い(コーディング短くしたい)。
clockはtimeと名前違いすぎてちょっと気になる。特徴
名前:timeっぽく、でも短い名前にしよう。 → xtime
機能:絞りに絞る! → ふたつだけ。
xtime.Now()
って使うだけ。
テストの時はxtime.Mock( ${xtime.Now()で返ってくる値} )
で時間変えるだけ。
テストの時はxtime.Mock( ${xtime.Now()で実行する関数} )
で時間変えるだけ。
使い方:
既存のソースでtime.Now()を使っている部分をこれに置き換えれば使えます。〜〜修正:2021/01/16 初回投稿後〜〜
より柔軟に設定できるようxtime.Mock
の引数を値→関数に変えました。
これでテストケースに記載したような実行毎に時間が進むMockを作ったり、Mockをリセットしたりできるようになりました。git公開してますが、以下にソースも載せます。
ソース
xtime.gopackage xtime import ( "time" ) var now func() time.Time = time.Now // Mock overwrites return value of xtime.Now(). // You must not use this function except for in test. func Mock(fn func() time.Time) { now = fn } // Now returns the value of time.Now(). // If the Mock function has been executed in advance, the value set by Mock is returned. func Now() time.Time { return now() }
テスト
xtime_test.gopackage xtime_test import ( "testing" "time" "xtime" ) type incrementalMock struct { i time.Duration t time.Time } func (m *incrementalMock) Now() time.Time { m.i++ return m.t.Add(time.Second * m.i) } func TestNow(t *testing.T) { // case 1. // Default xtime.Now behavior // 'xtime.Now() == time.Now()' become false due to the μ second level execution time difference. if !(xtime.Now().Sub(time.Now()) <= time.Second*1) { t.Error("Default xtime.Now() must be same to time.Now().") } // case 2. // Constant value Mock mockTime := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC) xtime.Mock(func() time.Time { return mockTime }) if xtime.Now() != mockTime { t.Error("xtime.Now() must be same to MockTime.") } // case 3. // Incremental value Mock incMock := &incrementalMock{ i: 0, t: mockTime, } xtime.Mock(incMock.Now) if xtime.Now().Sub(mockTime) != time.Second*1 { t.Error("xtime.Now() must be same to MockTime+1sec.") } if xtime.Now().Sub(mockTime) != time.Second*2 { t.Error("xtime.Now() must be same to MockTime+2sec.") } // case 4. // reset xtime.Mock(time.Now) // Same to 1st test case. if !(xtime.Now().Sub(time.Now()) <= time.Second*1) { t.Error("Default xtime.Now() must be same to time.Now().") } }