20210116のGoに関する記事は6件です。

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.mod
module xxxx

// +heroku goVersion go1.15      <--追加
go 1.15

のように記述する必要があるようだ。
書き直してpushしなおしてみると、無事デプロイできた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

キーエンス プログラミング コンテスト 2021のメモ

前置き

Atcoderをやってみたので、自分用のメモです。
あとから加筆・修正する予定です。

問題

https://atcoder.jp/contests/keyence2021

A

Q_A.go
package 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.go
package 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

覚えてたら後で書きます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オレオレ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はここを変えた)

gofmtimportブロック内の空行で区切られたパッケージ群をそれぞれ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/modgolang.org/x/toolsだけですが、golang.org/x/toolsはgitのタグが切られておらずリリースは最新のコミットハッシュが更新されるだけです。
Renovate はタグのリリースだけではなくコミットハッシュの更新にも対応しているので、ちゃんと最新に追従することができます。
あと、masterブランチにPRがマージされるたびにリリースノートに追記してくれる release-drafter もとても便利でした。

gosimportsという命名は割と気に入っている

gosimports = simpler + goimportsです。
結構気に入っています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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()を利用してテストを平行に走らせることも可能です。
  • ちなみに今回の例では環境変数を利用することで、ローカル開発用に使っているデータベースとは別のものを使ってテストを行うようにしていますので、詳しくは Makefiletestを見てみてください。
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と合わせて結構いい感じにプロジェクト全体のテストが書けそうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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 $SHELL

goenvのバージョンを確認します。

% 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.6

GOのバージョンを確認します。

% 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 <プロジェクト名>で進めていきましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.go
package 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.go
package 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().")
    }

}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む