20200811のGoに関する記事は13件です。

【Go】【Ginkgo】FizzBuzzをGinkgoでテストしてみる

はじめに

最近Goの勉強をしてて、簡単なWebサーバーを作ってみたり、コマンドラインで動作するツールを作ったり、、、としているんですけど、そういやテスト書いたことないなあと思って今回の記事を書くことにしました!

Ginkgoを選んだ理由

Ginkgoの書き方がなんとなくrspecに似てるからです。
昔Railsを書いていたことがあるので、簡単に書けるんじゃないかな〜っていう安易な発想です。

テストの準備

Ginkgoをインストールします。

go get github.com/onsi/ginkgo/ginkgo
go get github.com/onsi/gomega/...

ディレクトリとテスト用ファイルを作成します。

mkdir fizzbuzz
cd fizzbuzz
ginkgo bootstrap
ginkgo generate fizzbuzz

実装

今回作成するテストケースは以下の通りとします。よく見るFizzBuzzですね。

  • 数値が3の倍数であれば「Fizz」を返す
  • 数値が5の倍数であれば「Buzz」を返す
  • 数値が3の倍数かつ5の倍数であれば「FizzBuzz」を返す
  • 数値が3の倍数でも5の倍数でもなければ、そのまま文字列に変換して返す

早速ですが実装は以下の通りです。
シンプルなFizzBuzzなので特に何もないですね。

fizzbuzz.go
package fizzbuzz

import (
    "strconv"
)

func Convert(i int) (string, error) {
    if i%15 == 0 {
        return "FizzBuzz", nil
    }
    if i%3 == 0 {
        return "Fizz", nil
    }
    if i%5 == 0 {
        return "Buzz", nil
    }
    return strconv.Itoa(i), nil
}

こちらがテストコードになります。
こういう書き方がいいのかダメなのかもわかってないですけど、それっぽく書いてみました。

fizzbuzz_test.go
package fizzbuzz

import (
    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"

    "strconv"
)

var _ = Describe("fizzbuzz", func() {

    Describe("Convertメソッド", func() {

        Context("引数が3の倍数でも5の倍数でもない場合", func() {
            data := []int{1, 4, 8, 11, 13, 14, 17, 19, 23, 26}
            for idx := range data {
                // rangeの第2引数はポインタなのでそのままアサーションに使用すると正しくテストが実行されない
                v := data[idx]
                r, err := Convert(v)
                It("そのまま文字列変換した値を返すこと", func() {
                    Expect(r).To(Equal(strconv.Itoa(v)))
                })
                It("エラーがnilであること", func() {
                    Expect(err).To(BeNil())
                })
            }
        })

        Context("引数が3の倍数", func() {
            data := []int{3, 6, 9, 12, 18, 21, 24, 27, 33, 36}
            for idx := range data {
                // rangeの第2引数はポインタなのでそのままアサーションに使用すると正しくテストが実行されない
                v := data[idx]
                r, err := Convert(v)
                It("Fizzという文字列を返すこと", func() {
                    Expect(r).To(Equal("Fizz"))
                })
                It("エラーがnilであること", func() {
                    Expect(err).To(BeNil())
                })
            }
        })

        Context("引数が5の倍数", func() {
            data := []int{5, 10, 20, 25, 35, 40, 50, 55, 65, 70}
            for idx := range data {
                // rangeの第2引数はポインタなのでそのままアサーションに使用すると正しくテストが実行されない
                v := data[idx]
                r, err := Convert(v)
                It("Buzzという文字列を返すこと", func() {
                    Expect(r).To(Equal("Buzz"))
                })
                It("エラーがnilであること", func() {
                    Expect(err).To(BeNil())
                })
            }
        })

        Context("引数が3の倍数かつ5の倍数", func() {
            data := []int{15, 30, 45, 60, 75, 90, 105, 120, 135, 150}
            for idx := range data {
                // rangeの第2引数はポインタなのでそのままアサーションに使用すると正しくテストが実行されない
                v := data[idx]
                r, err := Convert(v)
                It("FizzBuzzという文字列を返すこと", func() {
                    Expect(r).To(Equal("FizzBuzz"))
                })
                It("エラーがnilであること", func() {
                    Expect(err).To(BeNil())
                })
            }
        })

    })

})

おわりに

今回は、Goで簡単なテストを書いてみました。
標準のテストを書いたことがない状態で、いきなりGinkgoを書いていいものか悩みましたが、まあよしとしましょう。
何かの参考になれば幸いです。

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

nuxtのformからPOSTされたデータをgoのサーバで受け取る(私的メモ)

nuxt.js(vueファイル)

Vuetifyのコンポーネントを活用し、formを作成、validationとresetボタンを実装しました。

<template>
  <v-form ref="form" v-model="valid">
    <v-text-field
      v-model="title"
      :counter="10"
      :rules="titleRules"
      label="Title"
      required
    ></v-text-field>

    <v-textarea
      v-model="description"
      :counter="100"
      :rules="descriptionRules"
      label="Description"
      required
    ></v-textarea>

    <v-textarea
      v-model="content"
      :counter="100"
      :rules="contentRules"
      label="Content"
      required
    ></v-textarea>

    <v-btn :disabled="!valid" color="success" class="mr-4" @click="submit">
      SUBMIT
    </v-btn>

    <v-btn color="error" class="mr-4" @click="reset">
      Reset Form
    </v-btn>
  </v-form>
</template>

<script>
export default {
  data: () => ({
    valid: false,
    title: '',
    titleRules: [
      (v) => !!v || 'Title is required',
      (v) => (v && v.length <= 20) || 'Title must be less than 20 characters',
    ],
    description: '',
    descriptionRules: [
      (v) => !!v || 'Description is required',
      (v) =>
        (v && v.length <= 100) ||
        'Description must be less than 100 characters',
    ],
    content: '',
    contentRules: [
      (v) => !!v || 'Content is required',
      (v) =>
        (v && v.length <= 100) || 'Content must be less than 100 characters',
    ],
  }),
  methods: {
    post() {
      this.$axios.$post('http://localhost:10000/articles', {
        title: this.title,
        desc: this.description,
        content: this.content,
      })
    },
    submit() {
      this.$refs.form.validate()
      if (this.valid) {
        this.post()
        alert('' + this.title + '」を登録しました。')
        document.location = '/articles'
      }
    },
    reset() {
      this.$refs.form.reset()
    },
  },
}
</script>

validationに合格するとsubmitボタンの色が変化し、ボタンをクリックするとイベントが発火しsubmit()が呼ばれます
その中で、post()メソッドを呼び出し$axiosモジュールを使ってAPIサーバにPOSTメソッドをデータ付きで送信します。axiosはデフォルトでContent-Typeをapplication/jsonで通信します。

go

# CORS対策
func withCORS(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Header().Set("Access-Control-Allow-Origin", "*")
        fn(w, r)
    }
} 

# muxにハンドラを登録している部分です(gorilla/muxを使用)
myRouter.HandleFunc("/articles", withVars(withDB(db, withCORS(createNewArticle)))).Methods("POST")



# 登録してるハンドラ定義部分です
func createNewArticle(w http.ResponseWriter, r *http.Request) {
    log.Println("called createNewArticle")

    # db接続
    d := GetVar(r, "db").(Database)
    db := d.init()

    # JsonをArticle構造体としてパース
    article := ParseJsonArticle(w, r)

    # 検証と保存
    if valid := article.validate(); valid {
        db.Create(&article)
        if db.NewRecord(article) {
            log.Println("新規articleの保存に失敗しました。")
        }
    }
}

フォームから適当なデータを送ってみると....

(一部)
Access to XMLHttpRequest at 'http://localhost:10000/articles' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
xhr.js:178 POST http://localhost:10000/articles net::ERR_FAILED

CORSポリシーでクライアントからサーバーへのリクエストがブロックされていると怒られています。
特にこの部分、
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

'Access-Control-Allow-Origin' ヘッダーはgoでハンドラの直前にチェインさせている関数(withCORS)で確かに付与しているハズ...

(。´・ω・)ん?

go側のログを見る限り、ハンドラ関数に到達すらしていない模様。

こんな記事がありました。
https://qiita.com/att55/items/2154a8aad8bf1409db2b

プリフライトリクエストとシンプルリクエストなんてものがあるようです。

Content-Type=''application/json"の場合、クロスオリジン(ドメインかポートがサーバとクライアントで違う)の場合、一旦OPTIONSメソッドが走り、安全確認できたら本メソッドが走るイメージかなと理解します。
https://github.com/gorilla/mux#user-content-handling-cors-requests
に沿って、サーバ側のコードを直します。

func articlesCORSHandling(w http.ResponseWriter, r *http.Request){
    # レスポンスヘッダーの追加
    w.Header().Set("Access-Control-Allow-Origin","http://localhost:3000")
    w.Header().Set( "Access-Control-Allow-Headers", "Origin, ContentType,")
    w.Header().Set( "Access-Control-Allow-Methods","GET, POST, OPTIONS" )

    # GETメソッド
    if r.Method == http.MethodGet {
        log.Println("called GET /articles")
        d := GetVar(r, "db").(Database)
        db := d.init()
        var articles Articles
        db.Find(&articles)
        json.NewEncoder(w).Encode(articles)
    }

    # POST メソッド
    if r.Method == http.MethodPost {
        log.Println("called POST /articles")
        d := GetVar(r, "db").(Database)
        db := d.init()
        article := ParseJsonArticle(w, r)
        log.Println(article)
        if valid := article.validate(); valid {
            // log.Println("valid!!")
            db.Create(&article)
            if db.NewRecord(article) == false {
                log.Println("新規articleを保存しました")
            }else {
                log.Println("新規articleの保存に失敗しました。")
            }
        }
    }

}

func handleRequests() {
    r := mux.NewRouter()
    r.HandleFunc("/articles", withVars(withDB(db, articlesCORSHandling))).Methods(http.MethodGet, http.MethodPost, http.MethodOptions)
    r.Use(mux.CORSMethodMiddleware(r))
    log.Fatal(http.ListenAndServe(":10000", r))
}

withCORSを関数内部に統合させる形でハンドラを定義し登録してます。
再度、適当なデータを送信してみると...

2020/08/11 12:44:48 called POST /articles
2020/08/11 12:44:48 {{0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC <nil>} test test test}
2020/08/11 12:44:48 新規articleを保存しました

成功しました。

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

Go構造体の`json:hoge`の正体を探る

TL;DR

type Person struct {
    Name string `json:"name"` 
}
  • `json:"name"` は構造体のタグ
  • reflect.TypeOf(Person).Field(0).Get("json")で取り出せる!

はじめに

Go言語でjsonを扱うとき、構造体定義にjsonのフィールド名を書くと思います。

type Person struct {
    Name    string `json:"name"`
    Age     int    `json:"age"`
    Address string `json:"address"`
}

おまじないと思って深く考えず使っていましたが、

  • そもそも`json:"name"`って何?
  • ただのコメント?
  • jsonのためだけの特別構文?

という疑問がわいたので調べてみました。

json:hoge は構造体のタグ

公式ドキュメントに早速答えがありました。ちゃんとふだんから読みなさい

The Go Programming Language Specification - The Go Programming Language

A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored.

フィールド宣言の後にはオプションで文字列リテラルのタグを付けることが可能で、これはフィールド宣言に対応する全てのフィールドの属性となります。空文字列のタグはタグ無しの場合と同等です。タグはリフレクションインターフェースにより可視化され、構造体の型の一意性1に関与しますが、それ以外の場合は無視されます。
(日本語は拙訳)

`json:"hoge"`は構造体のタグであり、もともとGo言語にある構文を使っている(json用の特別なものではない)ことが分かります。

タグの取り出し方

ドキュメントの通り、タグはreflectを使って取り出すことができます。

import (
    "fmt"
    "reflect"
)

type Person struct {
    Name string `my name`
    Age  int    `Don't look at this!`
}

func main() {
    person := Person{Name: "Taro", Age: 23}

  // リフレクションで型情報取得
  personType := reflect.TypeOf(person)
    for i := 0; i < personType.NumField(); i++ {
    // タグを取得
    tag := personType.Field(i).Tag
    fmt.Println(tag)
    }
}
result
my name
Don't look at this!

タグ内のキー

これだけでも便利ですが、タグをkey:"value"の形式で書くことで複数の属性を持たせることができます。キーに対応する値はTag.Get(key)で取り出せます。
encoding/jsonはタグ中のjsonキーを参照していたのですね。

import (
    "fmt"
    "reflect"
)

type Person struct {
    Name    string `json:"name" `
    Age     int    `json:"age"`
  // 他のキーも定義
    Address string `json:"address" description:"where he lives" country:"Japan"`
}

func main() {
    person := Person{Name: "Taro", Age: 23, Address: "Tokyo"}

    personType := reflect.TypeOf(person)
    fmt.Println(personType.Field(0).Tag.Get("json")) // name
    fmt.Println(personType.Field(1).Tag.Get("json")) // age
    fmt.Println(personType.Field(2).Tag.Get("json")) // address
    // 他のキーも参照可能!
    fmt.Println(personType.Field(2).Tag.Get("description")) // where he lives
    fmt.Println(personType.Field(2).Tag.Get("country"))     // Japan
}

実際にjson.Marshalを読んでみると、(呼び出しを追うのが大変ですが)Tag.Get("json")でフィールド名を取り出す処理が確認できます。

Marshal定義箇所

フィールド名取り出し処理

タグを利用するライブラリ

json以外にもxml, yaml等色々なパッケージで利用されているみたいです。上手く活用していきたいですね!

Well known struct tags · golang/go Wiki · GitHub


  1. type identityは2つの型を同一視する条件のようです(参考)。訳語が既にあったらコメント等で教えていただけるとありがたいです。 

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

Golang + Sonarqube + Codebuild

https://qiita.com/fake-deli-ca/items/e01a42a9d04cf0bab8c4
の後にJenkinsじゃなくてCodeBuildも対応してみたんだけど、異様にわかりにくくて、時間かかった…。

結論

最初に結論から。
関係する箇所のみ抜粋してある。

buildspec.yml
version: 0.2

env:
  variables:
   GO_VERSION      : ""  
   PROJECT_VERSION : ""  
   # 本来はParameter-Storeにすべき
   SONARQUBE_TOKEN  : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"


phases:
  install:
    runtime-versions:
      golang: ${GO_VERSION}
    commands:
      - type go && go version
      - chmod +x ./gradlew
      - export REGOPATH=`echo $GOPATH | cut -c 5-`
      - export PATH=${GOPATH}/bin:${PATH} && echo ${PATH}

  pre_build:
    commands:
      - echo 'setup'
      - make setup

  build:
    commands:
      - echo 'make & test'
      - make
      - make test
      - ./gradlew sonarqube -Dsonar.projectBaseDir=${REGOPATH}/src -Dsonar.login=${SONARQUBE_TOKEN} -Dsonar.projectVersion=${PROJECT_VERSION} --info

下記のMakefileはBuildの記載がないので、このままでは動作しない。

.PHONY: all
all: build

.PHONY: setup
setup:
    go get -u github.com/ory/go-acc

.PHONY: test
test:
    go test ./... -json > reports/test.json
    go-acc ./...  -o reports/coverage.out
build.gradle
plugins {
    id "org.sonarqube" version "3.0"
}

sonarqube {
    properties {
        //FIXME 
        property "sonar.host.url","https://sonarqube.foo.jp"

        property "sonar.projectKey", "hogehoge"
        property "sonar.projectName", "hogehoge Project"

        property "sonar.go.coverage.reportPaths", "(gitのpass)/reports/coverage.out"
        property "sonar.go.tests.reportPaths", "(gitのpass)/reports/test.json"
        //FIXME

        property "sonar.sources", "."

        property "sonar.exclusions", "**/*_test.go"
        property "sonar.tests", "."
        property "sonar.test.inclusions", "**/*_test.go"
        property "sonar.test.exclusions", "**/vendor/**"
        property "sonar.sourceEncoding", "UTF-8"
    }
}

確認すべき点

is not included in the project, ignoring coverage

Sensor Go Cover sensor for Go coverage [go]
Load coverage report from '/codebuild/output/src532592822/src/(gitのpass)/coverage.txt'
File '/codebuild/output/src532592822/src/(gitのpass)/AAA/BBB.go' is not included in the project, ignoring coverage

とにかく、is not included in the project, ignoring coverageにやられた。どう設定していいのか全く分からない。
https://github.com/SonarSource/sonar-go/issues/218
を読むとGOPATHの設定が悪い、と書いてあるが、どうすれば治るのかは書いてない。
酷い…。

結論を言うと、sonar.projectBaseDirの設定が悪い。ところが、皆様ご存じの通り、Codebuildは/codebuild/output/srcXXXXXXXXXX/srcの部分が毎回異なるため、何らかの利用できる変数で設定しなければならない。
普通に考えるとsonar.projectBaseDir=$GOPATH/srcとすればよいのだが、ところがである。Codebuildの$GOPATH/go:/codebuild/output/src532592822となっており、/go: の部分が心底余計なのである。
上記のように設定すると、

* What went wrong:
Execution failed for task ':sonarqube'.
> The folder '/go:/codebuild/output/src179970974/src' does not exist for 'xxxxx' (base directory = /codebuild/output/src179970974/src/(gitのpass))

みたいなエラーになるのである。まあ当然だが。
というわけで強引にcutで削除して再設定した。本当にこれでいいのかどうかは知らない。
また、本来はbuild.gradleに全部書きたかったのだが、当然環境変数と展開できないので、-Dsonar.projectBaseDir=${REGOPATH}/srcという設定とした。

reportPathsの変更

上記projectBaseDirの変更に伴う対応である。
普通、この辺のPathは$CODEBUILD_SRC_DIRの直下に置かれるので、gitのpassは本来不要なのであるが、projectBaseDirを変更した関係上、ここも変更が必要である。

        property "sonar.go.coverage.reportPaths", "(gitのpass)/reports/coverage.out"
        property "sonar.go.tests.reportPaths", "(gitのpass)/reports/test.json"

Gradleの利用

sonarqubeだけgradle使っててなんかおかしな感じではあるのだが、最初こんな感じでやってたらunzipのインストールが必要だったり、sonar-scannerのインストール位置が気持ち悪かったり、なんだか訳がわからなくなりそうだったので、あきらめて素直にgradle使うことにした。別に使わなくてもいいとは思う。ただ↓の設定でうまくいくところまでは試してない。少なくともGOPATHはこのままでは駄目だと思う。
また、gogradleで全部書き直すことも考えたが、なんかそこまでして綺麗にする理由もなかったのでやめ。

buildspec.yml
phases:
  install:
    runtime-versions:
      golang: ${GO_VERSION}
    commands:
      - type go && go version
      # sonar-scanner install
      - yum -y install unzip
      - wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.4.0.2170-linux.zip
      - unzip ./sonar-scanner-cli-4.4.0.2170-linux.zip
  pre_build:
    commands:
      - echo 'setup'
      - export PATH=${GOPATH}/bin:./sonar-scanner-4.4.0.2170-linux/bin:${PATH} && echo ${PATH}
      - make setup

  build:
    commands:
      - echo 'make & test'
      - make
      - make test
      - sonar-scanner -X -Dsonar.login=${SONARQUBE_TOKEN}

go-accの利用

ここまで実施してみて、どうもCoverage率が異様に低いことに気が付く。
これも結論だけをいうと、go test -coverのデフォルト挙動に問題があるということである。なので、go-accを利用することとした。

The reason for this was that go test -cover is per default only recording code coverage for the package that is currently tested, not for all the packages from the project. If you have a lot of integration tests, your code coverage might be higher than you think!

https://www.ory.sh/golang-go-code-coverage-accurate/

…と結論だけなら簡単だが、ここに辿り着くまでに大層時間かかった。

I found a brutal hack of a workaround. I'm trying to report coverage on a project with a single golang module, and all of the module's files are below a single subdirectory in the git repo. We use go-acc to measure coverage, it writes file coverage.txt. I added a step in the build script to rewrite the prefix on the paths in that file, change the module name to the directory name, kinda like this:
sed -i -e 's/^mymodule/ModuleSubdir/' coverage.txt
Then the sonar scanner is happy. I don't know if the coverage numbers are correct, but they ain't zero no more

https://jira.sonarsource.com/browse/SONARSLANG-450
大本は、is not included in the project, ignoring coverageを解決すべく探していた際に見つけた上記であるが、結果的にソースコードのCoverage登録そのものとは関係なく、別の問題を解決することになった。

うーん。

なんかもうちょっと綺麗にしたい気分ではあるんだけど…。
というのと、情報が少なくてきついですね。

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

golang + sonarqube + codebuild

https://qiita.com/fake-deli-ca/items/e01a42a9d04cf0bab8c4
の後にJenkinsじゃなくてCodeBuildも対応してみたんだけど、異様にわかりにくくて、時間かかった…。

結論

最初に結論から。
関係する箇所のみ抜粋してある。

buildspec.yml
version: 0.2

env:
  variables:
   GO_VERSION      : ""  
   PROJECT_VERSION : ""  
   # 本来はParameter-Storeにすべき
   SONARQUBE_TOKEN  : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"


phases:
  install:
    runtime-versions:
      golang: ${GO_VERSION}
    commands:
      - type go && go version
      - chmod +x ./gradlew
      - export REGOPATH=`echo $GOPATH | cut -c 5-`
      - export PATH=${REGOPATH}/bin:${PATH} && echo ${PATH}

  pre_build:
    commands:
      - echo 'setup'
      - make setup

  build:
    commands:
      - echo 'make & test'
      - make
      - make test
      - ./gradlew sonarqube -Dsonar.projectBaseDir=${REGOPATH}/src -Dsonar.login=${SONARQUBE_TOKEN} -Dsonar.projectVersion=${PROJECT_VERSION} --info

下記のMakefileはBuildの記載がないので、このままでは動作しない。

.PHONY: all
all: build

.PHONY: setup
setup:
    go get -u github.com/ory/go-acc

.PHONY: test
test:
    go test ./... -json > reports/test.json
    go-acc ./...  -o reports/coverage.out
build.gradle
plugins {
    id "org.sonarqube" version "3.0"
}

sonarqube {
    properties {
        //FIXME 
        property "sonar.host.url","https://sonarqube.foo.jp"

        property "sonar.projectKey", "hogehoge"
        property "sonar.projectName", "hogehoge Project"

        property "sonar.go.coverage.reportPaths", "(gitのpass)/reports/coverage.out"
        property "sonar.go.tests.reportPaths", "(gitのpass)/reports/test.json"
        //FIXME

        property "sonar.sources", "."

        property "sonar.exclusions", "**/*_test.go"
        property "sonar.tests", "."
        property "sonar.test.inclusions", "**/*_test.go"
        property "sonar.test.exclusions", "**/vendor/**"
        property "sonar.sourceEncoding", "UTF-8"
    }
}

確認すべき点

is not included in the project, ignoring coverage

Sensor Go Cover sensor for Go coverage [go]
Load coverage report from '/codebuild/output/src532592822/src/(gitのpass)/coverage.txt'
File '/codebuild/output/src532592822/src/(gitのpass)/AAA/BBB.go' is not included in the project, ignoring coverage

とにかく、is not included in the project, ignoring coverageにやられた。どう設定していいのか全く分からない。
https://github.com/SonarSource/sonar-go/issues/218
を読むとGOPATHの設定が悪い、と書いてあるが、どうすれば治るのかは書いてない。
酷い…。

結論を言うと、sonar.projectBaseDirの設定が悪い。ところが、皆様ご存じの通り、Codebuildは/codebuild/output/srcXXXXXXXXXX/srcの部分が毎回異なるため、何らかの利用できる変数で設定しなければならない。
普通に考えるとsonar.projectBaseDir=$GOPATH/srcとすればよいのだが、ところがである。Codebuildの$GOPATH/go:/codebuild/output/src532592822となっており、/go: の部分が心底余計なのである。
上記のように設定すると、

* What went wrong:
Execution failed for task ':sonarqube'.
> The folder '/go:/codebuild/output/src179970974/src' does not exist for 'xxxxx' (base directory = /codebuild/output/src179970974/src/(gitのpass))

みたいなエラーになるのである。まあ当然だが。
というわけで強引にcutで削除して再設定した。本当にこれでいいのかどうかは知らない。
また、本来はbuild.gradleに全部書きたかったのだが、当然環境変数と展開できないので、-Dsonar.projectBaseDir=${REGOPATH}/srcという設定とした。

reportPathsの変更

上記projectBaseDirの変更に伴う対応である。
普通、この辺のPathは$CODEBUILD_SRC_DIRの直下に置かれるので、gitのpassは本来不要なのであるが、projectBaseDirを変更した関係上、ここも変更が必要である。

        property "sonar.go.coverage.reportPaths", "(gitのpass)/reports/coverage.out"
        property "sonar.go.tests.reportPaths", "(gitのpass)/reports/test.json"

Gradleの利用

sonarqubeだけgradle使っててなんかおかしな感じではあるのだが、最初こんな感じでやってたらunzipのインストールが必要だったり、sonar-scannerのインストール位置が気持ち悪かったり、なんだか訳がわからなくなりそうだったので、あきらめて素直にgradle使うことにした。別に使わなくてもいいとは思う。ただ↓の設定でうまくいくところまでは試してない。少なくともGOPATHはこのままでは駄目だと思う。
また、gogradleで全部書き直すことも考えたが、なんかそこまでして綺麗にする理由もなかったのでやめ。

buildspec.yml
phases:
  install:
    runtime-versions:
      golang: ${GO_VERSION}
    commands:
      - type go && go version
      # sonar-scanner install
      - yum -y install unzip
      - wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.4.0.2170-linux.zip
      - unzip ./sonar-scanner-cli-4.4.0.2170-linux.zip
  pre_build:
    commands:
      - echo 'setup'
      - export PATH=${GOPATH}/bin:./sonar-scanner-4.4.0.2170-linux/bin:${PATH} && echo ${PATH}
      - make setup

  build:
    commands:
      - echo 'make & test'
      - make
      - make test
      - sonar-scanner -X -Dsonar.login=${SONARQUBE_TOKEN}

go-accの利用

ここまで実施してみて、どうもCoverage率が異様に低いことに気が付く。
これも結論だけをいうと、go test -coverのデフォルト挙動に問題があるということである。なので、go-accを利用することとした。

The reason for this was that go test -cover is per default only recording code coverage for the package that is currently tested, not for all the packages from the project. If you have a lot of integration tests, your code coverage might be higher than you think!

https://www.ory.sh/golang-go-code-coverage-accurate/

…と結論だけなら簡単だが、ここに辿り着くまでに大層時間かかった。

I found a brutal hack of a workaround. I'm trying to report coverage on a project with a single golang module, and all of the module's files are below a single subdirectory in the git repo. We use go-acc to measure coverage, it writes file coverage.txt. I added a step in the build script to rewrite the prefix on the paths in that file, change the module name to the directory name, kinda like this:
sed -i -e 's/^mymodule/ModuleSubdir/' coverage.txt
Then the sonar scanner is happy. I don't know if the coverage numbers are correct, but they ain't zero no more

https://jira.sonarsource.com/browse/SONARSLANG-450
大本は、is not included in the project, ignoring coverageを解決すべく探していた際に見つけた上記であるが、結果的にソースコードのCoverage登録そのものとは関係なく、別の問題を解決することになった。

うーん。

なんかもうちょっと綺麗にしたい気分ではあるんだけど…。
というのと、情報が少なくてきついですね。

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

【Go】ユニットテスト

下記のコースのアウトプットになります。
Unit testing for Go developers | Udemy

参考にした記事
Goでテストを書く(テストの実装パターン集) - Qiita

Golang のテスト

実行コマンド

go test ./...

詳細を知りたい場合は下記
go test -v ./...

メッセージ

TODO

Table driven Test

Table Driven Testとは、それぞれの行が入力と期待する出力を含んだテストケースになっているテーブルを作り、それをループして実行する形式のテストです。
DMMにおけるユーザーレビュー基盤の変革(Goのテスト技法編) - DMM inside

テスト対象のコード

package calculator

import "errors"

type DiscountCalculator struct {
    minimumPurchaseAmount int
    discountAmount        int
}

func NewDiscountCalculator(minimumPurchaseAmount int, discountAmount int) (*DiscountCalculator, error) {
    // discountRepository database.Discount
    if minimumPurchaseAmount == 0 {
        return &DiscountCalculator{}, errors.New("minimum purchase amount could not be zero")
    }
    return &DiscountCalculator{
        minimumPurchaseAmount: minimumPurchaseAmount,
        discountAmount:        discountAmount,
    }, nil
}

func (c *DiscountCalculator) Calculate(purchaseAmount int) int {
    if purchaseAmount > c.minimumPurchaseAmount {
        multiplier := purchaseAmount / c.minimumPurchaseAmount
        return purchaseAmount - (c.discountAmount * multiplier)
    }

    return purchaseAmount
}

下記でもわかる通り、テストデータが非常にわかりやすいです。

package calculator

import "testing"

// testingの宣言は下記のような感じ
func TestDiscountCalculator(t *testing.T) {
    type testCase struct {
        name                  string
        minimumPurchaseAmount int
        discount              int
        purchaseAmount        int
        exepectedAmount       int
    }
    // テストケーズをまとめているところ
    // テストケースが一目でわかりやすい
    testCases := []testCase{
        {name: "should apply 20", minimumPurchaseAmount: 100, discount: 20, purchaseAmount: 150, exepectedAmount: 130},
        {name: "should apply 40", minimumPurchaseAmount: 100, discount: 20, purchaseAmount: 200, exepectedAmount: 160},
        {name: "should apply 60", minimumPurchaseAmount: 100, discount: 20, purchaseAmount: 350, exepectedAmount: 290},
        {name: "should not apply", minimumPurchaseAmount: 100, discount: 20, purchaseAmount: 50, exepectedAmount: 50},
    }

    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            calculator := NewDiscountCalculator(tc.minimumPurchaseAmount, tc.discount)
            amount := calculator.Calculate(tc.purchaseAmount)

            if amount != tc.exepectedAmount {
                t.Errorf("exepected %v, got %v", tc.exepectedAmount, amount)
            }
        })
    }
}
// 実行結果
=== RUN   TestDiscountCalculator
=== RUN   TestDiscountCalculator/should_apply_20
=== RUN   TestDiscountCalculator/should_apply_40
=== RUN   TestDiscountCalculator/should_apply_60
=== RUN   TestDiscountCalculator/should_not_apply
--- PASS: TestDiscountCalculator (0.00s)
    --- PASS: TestDiscountCalculator/should_apply_20 (0.00s)
    --- PASS: TestDiscountCalculator/should_apply_40 (0.00s)
    --- PASS: TestDiscountCalculator/should_apply_60 (0.00s)
    --- PASS: TestDiscountCalculator/should_not_apply (0.00s)

golangでテストを書くならテーブルドリブンテスト - Qiita

例外処理

部分的にテストをする場合は、

go test -v

Assert

assert.Equal(t, tc.exepectedAmount, amount)

詳しくは下記で。
GitHub - stretchr/testify: A toolkit with common assertions and mocks that plays nicely with the standard library

Mock(モック)

下記記事が一番わかりやすいです。
testify/mockでgolangのテストを書く - Qiita

// Mock の宣言
type DiscountRepositoryMock struct {
    mock.Mock
}

// interface 宣言
type Discount interface {
    FindCurrentDiscount() int
}

// Mockを使ったメソッドの呼び出し
func (drm DiscountRepositoryMock) FindCurrentDiscount() int {
    // 条件にあったReturn設定値を返す
    args := drm.Called()
    return args.Int(0)
}

for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            // Mockを初期化
            discountRepositoryMock := DiscountRepositoryMock{}
            // 使用するメソッドと返り値を設定する          
            discountRepositoryMock.On("FindCurrentDiscount").Return(tc.discount)

            calculator, err := NewDiscountCalculator(tc.minimumPurchaseAmount, discountRepositoryMock)
            if err != nil {
                // FailNow + log
                t.Fatalf("could not instantiate the calculator %s", err.Error())
            }
            amount := calculator.Calculate(tc.purchaseAmount)

            assert.Equal(t, tc.exepectedAmount, amount)
        })
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】ユニットテストのMock、Table driven Testとか

下記のコースのアウトプットになります。
Unit testing for Go developers | Udemy

参考にした記事
Goでテストを書く(テストの実装パターン集) - Qiita

Golang のテスト

実行コマンド

go test ./...

詳細を知りたい場合は下記
go test -v ./...

メッセージ

TODO

Table driven Test

Table Driven Testとは、それぞれの行が入力と期待する出力を含んだテストケースになっているテーブルを作り、それをループして実行する形式のテストです。
DMMにおけるユーザーレビュー基盤の変革(Goのテスト技法編) - DMM inside

テスト対象のコードです。
下記からは、コースで使用したソースを使っていきます。

package calculator

import "errors"

type DiscountCalculator struct {
    minimumPurchaseAmount int
    discountAmount        int
}

func NewDiscountCalculator(minimumPurchaseAmount int, discountAmount int) (*DiscountCalculator, error) {
    if minimumPurchaseAmount == 0 {
        return &DiscountCalculator{}, errors.New("minimum purchase amount could not be zero")
    }
    return &DiscountCalculator{
        minimumPurchaseAmount: minimumPurchaseAmount,
        discountAmount:        discountAmount,
    }, nil
}

func (c *DiscountCalculator) Calculate(purchaseAmount int) int {
    if purchaseAmount > c.minimumPurchaseAmount {
        multiplier := purchaseAmount / c.minimumPurchaseAmount
        return purchaseAmount - (c.discountAmount * multiplier)
    }

    return purchaseAmount
}

下記でもわかる通り、テストデータが非常にわかりやすいです。

package calculator

import "testing"

// testingの宣言は下記のような感じ
func TestDiscountCalculator(t *testing.T) {
    type testCase struct {
        name                  string
        minimumPurchaseAmount int
        discount              int
        purchaseAmount        int
        exepectedAmount       int
    }
    // テストケーズをまとめているところ
    // テストケースが一目でわかりやすい
    testCases := []testCase{
        {name: "should apply 20", minimumPurchaseAmount: 100, discount: 20, purchaseAmount: 150, exepectedAmount: 130},
        {name: "should apply 40", minimumPurchaseAmount: 100, discount: 20, purchaseAmount: 200, exepectedAmount: 160},
        {name: "should apply 60", minimumPurchaseAmount: 100, discount: 20, purchaseAmount: 350, exepectedAmount: 290},
        {name: "should not apply", minimumPurchaseAmount: 100, discount: 20, purchaseAmount: 50, exepectedAmount: 50},
    }

    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            calculator := NewDiscountCalculator(tc.minimumPurchaseAmount, tc.discount)
            amount := calculator.Calculate(tc.purchaseAmount)

            if amount != tc.exepectedAmount {
                t.Errorf("exepected %v, got %v", tc.exepectedAmount, amount)
            }
        })
    }
}
// 実行結果
=== RUN   TestDiscountCalculator
=== RUN   TestDiscountCalculator/should_apply_20
=== RUN   TestDiscountCalculator/should_apply_40
=== RUN   TestDiscountCalculator/should_apply_60
=== RUN   TestDiscountCalculator/should_not_apply
--- PASS: TestDiscountCalculator (0.00s)
    --- PASS: TestDiscountCalculator/should_apply_20 (0.00s)
    --- PASS: TestDiscountCalculator/should_apply_40 (0.00s)
    --- PASS: TestDiscountCalculator/should_apply_60 (0.00s)
    --- PASS: TestDiscountCalculator/should_not_apply (0.00s)

golangでテストを書くならテーブルドリブンテスト - Qiita

例外処理

TODO

Assert

assert.Equal(t, tc.exepectedAmount, amount)

詳しくは下記で。
GitHub - stretchr/testify: A toolkit with common assertions and mocks that plays nicely with the standard library

Mock(モック)

Mockは、必要なテスト条件を容易に作ることができます。
Mockでユニットテストを簡単にしよう! | GMOアドパートナーズグループ TECH BLOG byGMO

Golangでは、下記記事が一番わかりやすかったです。
testify/mockでgolangのテストを書く - Qiita

// Mock の宣言
type DiscountRepositoryMock struct {
    mock.Mock
}

// interface 宣言
type Discount interface {
    FindCurrentDiscount() int
}

// Mockを使ったメソッドの呼び出し
func (drm DiscountRepositoryMock) FindCurrentDiscount() int {
    // 条件にあったReturn設定値を返す
    args := drm.Called()
    return args.Int(0)
}

for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            // Mockを初期化
            discountRepositoryMock := DiscountRepositoryMock{}
            // 使用するメソッドと返り値を設定する          
            discountRepositoryMock.On("FindCurrentDiscount").Return(tc.discount)

            calculator, err := NewDiscountCalculator(tc.minimumPurchaseAmount, discountRepositoryMock)
            if err != nil {
                // FailNow + log
                t.Fatalf("could not instantiate the calculator %s", err.Error())
            }
            amount := calculator.Calculate(tc.purchaseAmount)

            assert.Equal(t, tc.exepectedAmount, amount)
        })
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoでバイナリからJPEG生成

Goでバイナリでファイルを受け取った際にそのまま加工したい時

結論
bytes パッケージを使おう
Go言語 パッケージ bytes

ソースコード

main.go
package main

import (
    "bytes"
    "fmt"
    "image"
    "image/jpeg"
    "io/ioutil"
    "os"
)

func main() {

    // ファイルの読み込み
    buf, _ := ioutil.ReadFile("input.jpg")

    // バイナリファイルをReaderに読み込み
    reader := bytes.NewReader(buf)

    // 画像変換
    img, _, err := image.Decode(reader)
    if err != nil {
        fmt.Errorf("err %v", err)
    }

    // ファイル作成
    f, err := os.Create("output.jpg")
    if err != nil {
        fmt.Errorf("err %v", err)
    }
    defer f.Close()

    opt := jpeg.Options{
        Quality: 90,
    }

    // 今回はJPEGだがPNGとかでもできそう
    if err = jpeg.Encode(f, img, &opt); err != nil {
        fmt.Errorf("err %v", err)
    }
}


参考

Go言語 パッケージ bytes Reader

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

Golang 基礎 (struct周り)part2

part1の続き

makeとnewの違い

どちらも値を入れずメモリ領域を確保する。違いはポインタが返ってくるかどうか。

// 返ってこない
s := make([]int, 0)
m := make(map[string]int)
ch := make(chan int)

// 返ってくる
var p *int = new(int)
var st = new(struct{})

cf. 中身はnil (メモリ確保していない)

var s []int
var m map[string]int
var ch chan int
var p *int 

struct

type Vertex struct {
    X, Y int
    S string
}

v1とv2は同じ意味。v3とv4は同じ意味。

v1 := Vertex{} // {0 0  }
var v2 Vertex // {0 0  }

v3 := new(Vertex) // &{0 0  }
v4 := &Vertex{} // &{0 0  }
    changeVertex := func(v Vertex) {
        v.X = 1000
    }
    changeVertex2 := func(v *Vertex) {
        v.X = 1000 // (*v).X = 1000 と同じ意味になる
    }

    v := Vertex{1, 2, "test"}

    changeVertex(v) // 値渡し
    fmt.Println(v) // {1 2 test} 値が書き換わっていない
    changeVertex2(&v) // 参照渡し
    fmt.Println(v) // {1000 2 test} 値が書き換わっている

structオリエンテッド

package main

import "fmt"

type Vertex struct {
    x, y int // 先頭が小文字だとpackage内からのみ参照可能
}

// メソッド(値レシーバー)
func (v Vertex) Area() int {
    return v.x * v.y
}

// メソッド(ポインタレシーバー) 中身を書き換える!
func (v *Vertex) Scale(i int) {
    v.x = v.x * i
    v.y = v.y * i
}

// Embedded(継承みたいなやつ)
type Vertex3D struct {
    Vertex
    z int
}

func (v Vertex3D) Area3D() int {
    return v.x * v.y * v.z
}

func (v *Vertex3D) Scale3D(i int) {
    v.x *= i
    v.y *= i
    v.z *= i
}

// コンストラクタ
func New(x, y, z int) *Vertex3D {
    return &Vertex3D{Vertex{x, y}, z}
}

func main() {
    v := New(4, 5, 6)
    v.Scale3D(10)
    fmt.Println(v.Area()) // 継承元のメソッドも使える
    fmt.Println(v.Area3D())
}

non-struct

自分なりの型とメソッドを作れる。

type MyInt int

func (i MyInt) Double() int {
    return int(i * 2)
}

インターフェースとダックタイピング

package main

import "fmt"

// 設計書みたいなイメージ
type Human interface {
    Say() string // 実装しないといけない関数を書く
}

type Person struct {
    Name string
}

func (p Person) Say() string {
    return "Mr." + p.Name
}

// インターフェースのダックタイピング(Say()というメソッドがないと成立しないときに使う)
func DriveCar(human Human) {
    if human.Say() == "Mr.Mike" {
        fmt.Println("Run")
    } else {
        fmt.Println("Get out")
    }
}

func main() {
    var mike Human = Person{"Mike"} // Humanにstructを入れたら、Say()というメソッドがないとだめ
    DriveCar(mike)
}

タイプアサーションとswitch type文

// 引数が interface{} →何でも引数として受け入れる!
func do(i interface{})  {
    /* タイプアサーション: interfaceを他の型に変える
    ii := i.(int)
    ss := i.(string) など
    */

    // switch type 文
    switch v := i.(type) {
    case int:
        fmt.Println(v * 2)
    case string:
        fmt.Println(v + "!!!!")
    default:
        fmt.Printf("I don't know %T.\n", v)

    }
}

Stringer

package main

import "fmt"

type Person struct {
    Name string
    Age int
}

// String()のメソッドを実装すれば fmt の出力を変えることができる!
func (p Person) String() string {
    return fmt.Sprintf("My name is %v, I'm %v years old.", p.Name, p.Age)
}

func main() {
    mike := Person{"Mike", 20}
    fmt.Println(mike) // My name is Mike, I'm 20 years old.
}

カスタムエラー

package main

import "fmt"

// 自分なりのエラーを作る
type UserNotFound struct {
    Username string
}

// 作ったエラーにError()のメソッドを実装するとエラー時にこれが出力される
func (e *UserNotFound) Error() string {
    return fmt.Sprintf("User not found: %v", e.Username)
}

func myFunc() error {
    // something wrong
    ok := false
    if ok {
        return nil
    }
    return &UserNotFound{Username: "mike"}
}


func main() {
    if err := myFunc(); err != nil{
        fmt.Println(err) // User not found: mike
    }
}

Error()をポインタレシーバーにする理由:エラーが違う場所で起きたときに比較判定するため。
以下サンプルコード。err == io.EOFの部分。
err == errors.New("EOF")としてもうまく行かない。→ r.Read()が io.EOFというエラーを返すため。

package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("Hello, Reader!")

    b := make([]byte, 8)
    for {
        n, err := r.Read(b)
        fmt.Printf("n=%v err=%v b=%v\n", n, err, b)
        fmt.Printf("b[:n]=%q\n", b[:n])
        if err == io.EOF{
            break
        }
    }
}

参考

現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発

part3

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

Golang 基礎 メモ part2

part1の続き

makeとnewの違い

どちらも値を入れずメモリ領域を確保する。違いはポインタが返ってくるかどうか。

// 返ってこない
s := make([]int, 0)
m := make(map[string]int)
ch := make(chan int)

// 返ってくる
var p *int = new(int)
var st = new(struct{})

cf. 中身はnil (メモリ確保していない)

var s []int
var m map[string]int
var ch chan int
var p *int 

struct

type Vertex struct {
    X, Y int
    S string
}

v1とv2は同じ意味。v3とv4は同じ意味。

v1 := Vertex{} // {0 0  }
var v2 Vertex // {0 0  }

v3 := new(Vertex) // &{0 0  }
v4 := &Vertex{} // &{0 0  }
    changeVertex := func(v Vertex) {
        v.X = 1000
    }
    changeVertex2 := func(v *Vertex) {
        v.X = 1000 // (*v).X = 1000 と同じ意味になる
    }

    v := Vertex{1, 2, "test"}

    changeVertex(v) // 値渡し
    fmt.Println(v) // {1 2 test} 値が書き換わっていない
    changeVertex2(&v) // 参照渡し
    fmt.Println(v) // {1000 2 test} 値が書き換わっている

structオリエンテッド

package main

import "fmt"

type Vertex struct {
    x, y int // 先頭が小文字だとpackage内からのみ参照可能
}

// メソッド(値レシーバー)
func (v Vertex) Area() int {
    return v.x * v.y
}

// メソッド(ポインタレシーバー) 中身を書き換える!
func (v *Vertex) Scale(i int) {
    v.x = v.x * i
    v.y = v.y * i
}

// Embedded(継承みたいなやつ)
type Vertex3D struct {
    Vertex
    z int
}

func (v Vertex3D) Area3D() int {
    return v.x * v.y * v.z
}

func (v *Vertex3D) Scale3D(i int) {
    v.x *= i
    v.y *= i
    v.z *= i
}

// コンストラクタ
func New(x, y, z int) *Vertex3D {
    return &Vertex3D{Vertex{x, y}, z}
}

func main() {
    v := New(4, 5, 6)
    v.Scale3D(10)
    fmt.Println(v.Area()) // 継承元のメソッドも使える
    fmt.Println(v.Area3D())
}

non-struct

自分なりの型とメソッドを作れる。

type MyInt int

func (i MyInt) Double() int {
    return int(i * 2)
}

インターフェースとダックタイピング

package main

import "fmt"

// 設計書みたいなイメージ
type Human interface {
    Say() string // 実装しないといけない関数を書く
}

type Person struct {
    Name string
}

func (p Person) Say() string {
    return "Mr." + p.Name
}

// インターフェースのダックタイピング(Say()というメソッドがないと成立しないときに使う)
func DriveCar(human Human) {
    if human.Say() == "Mr.Mike" {
        fmt.Println("Run")
    } else {
        fmt.Println("Get out")
    }
}

func main() {
    var mike Human = Person{"Mike"} // Humanにstructを入れたら、Say()というメソッドがないとだめ
    DriveCar(mike)
}

タイプアサーションとswitch type文

// 引数が interface{} →何でも引数として受け入れる!
func do(i interface{})  {
    /* タイプアサーション: interfaceを他の型に変える
    ii := i.(int)
    ss := i.(string) など
    */

    // switch type 文
    switch v := i.(type) {
    case int:
        fmt.Println(v * 2)
    case string:
        fmt.Println(v + "!!!!")
    default:
        fmt.Printf("I don't know %T.\n", v)

    }
}

Stringer

package main

import "fmt"

type Person struct {
    Name string
    Age int
}

// String()のメソッドを実装すれば fmt の出力を変えることができる!
func (p Person) String() string {
    return fmt.Sprintf("My name is %v, I'm %v years old.", p.Name, p.Age)
}

func main() {
    mike := Person{"Mike", 20}
    fmt.Println(mike) // My name is Mike, I'm 20 years old.
}

カスタムエラー

package main

import "fmt"

// 自分なりのエラーを作る
type UserNotFound struct {
    Username string
}

// 作ったエラーにError()のメソッドを実装するとエラー時にこれが出力される
func (e *UserNotFound) Error() string {
    return fmt.Sprintf("User not found: %v", e.Username)
}

func myFunc() error {
    // something wrong
    ok := false
    if ok {
        return nil
    }
    return &UserNotFound{Username: "mike"}
}


func main() {
    if err := myFunc(); err != nil{
        fmt.Println(err) // User not found: mike
    }
}

Error()をポインタレシーバーにする理由:エラーが違う場所で起きたときに比較判定するため。
以下サンプルコード。err == io.EOFの部分。
err == errors.New("EOF")としてもうまく行かない。→ r.Read()が io.EOFというエラーを返すため。

package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("Hello, Reader!")

    b := make([]byte, 8)
    for {
        n, err := r.Read(b)
        fmt.Printf("n=%v err=%v b=%v\n", n, err, b)
        fmt.Printf("b[:n]=%q\n", b[:n])
        if err == io.EOF{
            break
        }
    }
}

参考

現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発

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

Golang 基礎 メモ part1

part2

パッケージ管理

ターミナルで実行。go.modファイルが作成される。

go mod init github.com/${GITHUB_USER}/${PROJECT_NAME}

あとは go get するだけ。

実行環境(docker)

main.goがあるディレクトリに以下を置く。(go.sumは1回でもgo getしてる場合のみCOPYする)

Dockerfile
FROM golang:alpine

WORKDIR /go/src/app

COPY go.mod .
# COPY go.sum . 
RUN go mod download

COPY . .
docker-compose.yml
version: "3.7"

services:
  goapp:
    build: .
    tty: true
    volumes:
    - .:/go/src/app

以下のコマンドでコンテナの中に入る。(goappはサービス名)

~% docker-compose up -d
~% docker-compose exec goapp /bin/ash

コンテナ内でmain.goを実行。

/go/src/app # go run main.go

strconv パッケージ

string型と基本的なデータ型との間の変換をするパッケージ

i, err := strconv.Atoi("-42") // stringからintへ
s := strconv.Itoa(-42)  // intからstringへ

スライスのmakeとcap

cap: メモリに確保してる分

m := make([]int, 5)
n := make([]int, 0, 5)
fmt.Printf("len=%d cap=%d value=%v\n", len(m), cap(m), m) // len=5 cap=5 value=[0 0 0 0 0]
fmt.Printf("len=%d cap=%d value=%v\n", len(n), cap(n), n) // len=0 cap=5 value=[]
s1 := make([]int, 0) // 空のスライスをメモリに確保する(mapも同様)
var s2 []int // nil(メモリに確保しない。mapも同様)

map(辞書型)

m := map[string]int{"apple": 100, "banana": 200}
fmt.Println(m["nothing"]) // 0

v, ok := m["apple"] // 2つ目の返り値は受け取らなくてもおっけい
fmt.Println(v, ok) // 100 true

可変長引数

func foo(params ...int) {
    fmt.Printf("len=%d params=%v\n" , len(params), params)
    for _, param := range params {
        fmt.Println(param)
    }
}
func main() {
    foo() // len=0 params=[]
    foo(1, 2) // len=2 params=[1 2]

    s := []int{1, 2, 3}
    foo(s...) // len=3 params=[1 2 3]
}

forとrange

// 条件のみ
sum := 1
for sum < 500 {
    sum += sum
}
fmt.Println(sum)

// 無限ループ
for {
    fmt.Println("hello")
}

// range
l := []string{"python", "go", "java"}
for index, value := range l{
    fmt.Println(index, value)
}

m := map[string]int{"apple": 100, "banan": 200}
for key, value := range m{
    fmt.Println(key, value)
}
for key := range m{
    fmt.Println(key)
}
for _, value := range m{
    fmt.Println(value)
}

swicth

// 通常
os := "mac"
switch os {
case "windows":
    fmt.Println("windows!!")
case "mac":
    fmt.Println("mac!!")
default:
    fmt.Println("linux!!")
}
// 条件省略
t := time.Now()
switch {
case t.Hour() < 12:
    fmt.Println("morning")
case t.Hour() < 17:
    fmt.Println("afternoon")
default:
    fmt.Println("foooo")
}

panicとrecover

panicで強制終了してしまう処理の前にdeferでrecover()すると強制終了しなくなる!

func thirdPartyConnectDB() {
    panic("Unable to connect database.")
}

func save() {
    defer func() {
        s := recover()
        fmt.Println(s)
    }()
    thirdPartyConnectDB()
}

func main() {
    save()
    fmt.Println("ok?")
}

参考

現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発

part2

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

Golang 基礎 (環境構築など) part1

前準備や基本情報

パッケージ管理

ターミナルで実行。go.modファイルが作成される。

go mod init github.com/${GITHUB_USER}/${PROJECT_NAME}

あとは go get するだけ。

実行環境(docker)

main.goがあるディレクトリに以下を置く。(go.sumは1回でもgo getしてる場合のみCOPYする)

Dockerfile
FROM golang:alpine

WORKDIR /go/src/app

COPY go.mod .
# COPY go.sum . 
RUN go mod download

COPY . .
docker-compose.yml
version: "3.7"

services:
  goapp:
    build: .
    tty: true
    volumes:
    - .:/go/src/app

以下のコマンドでコンテナの中に入る。(goappはサービス名)

~% docker-compose up -d
~% docker-compose exec goapp /bin/ash

コンテナ内でmain.goを実行。

/go/src/app # go run main.go

publicとprivate

  • 命名規則で判別。(変数名、関数名など)そのため、基本的にキャピタルケースorパスカルケースで命名する。
    • public: 他のパッケージからでも呼び出せる。先頭を大文字にする!
    • private: パッケージ内でのみ呼び出せる。先頭は小文字。
var PublicVariable string = "Public" // 呼び出せる
var privateVariable string = "private" // 呼び出せない
  • ファイル名はスネークケース ex. line_api.go

基礎文法

strconv パッケージ

string型と基本的なデータ型との間の変換をするパッケージ

i, err := strconv.Atoi("-42") // stringからintへ
s := strconv.Itoa(-42)  // intからstringへ

スライスのmakeとcap

cap: メモリに確保してる分

m := make([]int, 5)
n := make([]int, 0, 5)
fmt.Printf("len=%d cap=%d value=%v\n", len(m), cap(m), m) // len=5 cap=5 value=[0 0 0 0 0]
fmt.Printf("len=%d cap=%d value=%v\n", len(n), cap(n), n) // len=0 cap=5 value=[]
s1 := make([]int, 0) // 空のスライスをメモリに確保する(mapも同様)
var s2 []int // nil(メモリに確保しない。mapも同様)

map(辞書型)

m := map[string]int{"apple": 100, "banana": 200}
fmt.Println(m["nothing"]) // 0

v, ok := m["apple"] // 2つ目の返り値は受け取らなくてもおっけい
fmt.Println(v, ok) // 100 true

可変長引数

func foo(params ...int) {
    fmt.Printf("len=%d params=%v\n" , len(params), params)
    for _, param := range params {
        fmt.Println(param)
    }
}
func main() {
    foo() // len=0 params=[]
    foo(1, 2) // len=2 params=[1 2]

    s := []int{1, 2, 3}
    foo(s...) // len=3 params=[1 2 3]
}

forとrange

// 条件のみ
sum := 1
for sum < 500 {
    sum += sum
}
fmt.Println(sum)

// 無限ループ
for {
    fmt.Println("hello")
}

// range
l := []string{"python", "go", "java"}
for index, value := range l{
    fmt.Println(index, value)
}

m := map[string]int{"apple": 100, "banan": 200}
for key, value := range m{
    fmt.Println(key, value)
}
for key := range m{
    fmt.Println(key)
}
for _, value := range m{
    fmt.Println(value)
}

swicth

// 通常
os := "mac"
switch os {
case "windows":
    fmt.Println("windows!!")
case "mac":
    fmt.Println("mac!!")
default:
    fmt.Println("linux!!")
}
// 条件省略
t := time.Now()
switch {
case t.Hour() < 12:
    fmt.Println("morning")
case t.Hour() < 17:
    fmt.Println("afternoon")
default:
    fmt.Println("foooo")
}

panicとrecover

panicで強制終了してしまう処理の前にdeferでrecover()すると強制終了しなくなる!

func thirdPartyConnectDB() {
    panic("Unable to connect database.")
}

func save() {
    defer func() {
        s := recover()
        fmt.Println(s)
    }()
    thirdPartyConnectDB()
}

func main() {
    save()
    fmt.Println("ok?")
}

参考

現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発
https://xblood.hatenablog.com/entry/2019/01/28/213223

part2

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

ProxyConnectHeaderを使ってhttp.ClientによるCONNECTメソッドにヘッダーを付加する

事象

Go 1.14.2の環境で、認証Proxyを経由して任意のサイトにアクセスするHTTPクライアントを実装していたところ、接続先がHTTPSのときに認証proxyから407が返ってしまいました。

再現コード(main.go)
func main() {
    u, _ := url.Parse("http://127.0.0.1:3128") // ローカルにsquidを立て、認証Proxyとした
    req, err := http.NewRequest("GET", "https://yahoo.co.jp", nil)
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Add("Proxy-Authorization", "Basic <basic auth string>")
    c := &http.Client{
        Transport: &http.Transport{
            Proxy: http.ProxyURL(u),
        },
    }
    resp, err := c.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(resp.Status)
}

上記を実行すると、 407 (Proxy Authentication Required) が返って通信ができません。 Proxy-Authorization に然るべき認証情報は正しくセットしているのですが、なぜ通信できないのでしょうか。

$ go run main.go
2020/08/09 16:02:34 Get https://www.yahoo.co.jp/: Proxy Authentication Required
exit status 1

原因

接続先がHTTPSの場合、接続先とのTLSハンドシェイクの前に、仲介者である認証Proxyに対して CONNECT メソッドが発行されます。再現コードでヘッダーとして付与していた Proxy-Authorization が、このCONNECTメソッド実行時に付与されなかったため、本事象が発生していました。

CONNECTの実行は、内部的には http.TransportdialConn メソッドにて実行されています。このメソッドでは、CONNECTを行うために http.Request を新たに生成していますが、ここではもともと再現コードでProxy-AuthorizationをセットしたHeaderを使いません。その代わり、 http.TransportProxyConnectHeader という項目の値をセットしています。

http.Transport#dialConnより抜粋(transport.go#L1564-L1579)
    case cm.targetScheme == "https":
        conn := pconn.conn
        hdr := t.ProxyConnectHeader
        if hdr == nil {
            hdr = make(Header)
        }
        if pa := cm.proxyAuth(); pa != "" {
            hdr = hdr.Clone()
            hdr.Set("Proxy-Authorization", pa)
        }
        connectReq := &Request{
            Method: "CONNECT",
            URL:    &url.URL{Opaque: cm.targetAddr},
            Host:   cm.targetAddr,
            Header: hdr,
        }

このCONNECT時に元のHeaderを使わない仕様、私は最初ちょっと意外に感じたのですが、考えてみれば、元々の要求でセットしていたHeaderはあくまで本来の接続先のために用意しているものなので、認証Proxyとの会話であるCONNECT時にそれを渡してはならないですね。不要ですし、何よりセキュリティの観点から望ましくないです。

ProxyConnectHeader は、当然ながら http.Transport のコメントでも説明されています。

    // ProxyConnectHeader optionally specifies headers to send to
    // proxies during CONNECT requests.
    ProxyConnectHeader Header // Go 1.8

https://golang.org/pkg/net/http/#Transport

対応

http.Request.Header にセットしていた値を、 ProxyConnectHeader に入れてあげるよう変更すれば、事象の解消が可能です。

main.go
func main() {
    u, _ := url.Parse("http://127.0.0.1:3128")
    req, err := http.NewRequest("GET", "https://yahoo.co.jp", nil)
    if err != nil {
        log.Fatal(err)
    }
    hdr := make(http.Header)
    hdr.Add("Proxy-Authorization", "Basic <basic auth string>")
    c := &http.Client{
        Transport: &http.Transport{
            Proxy:              http.ProxyURL(u),
            ProxyConnectHeader: hdr,
        },
    }
    resp, err := c.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(resp.Status)
}

いけましたね。

$ go run main.go
200 OK

また、Proxy-Authorization に固定の認証情報をセットしたいだけなら、ProxyのURLに入れても良いですね。

func main() {
    u, _ := url.Parse("http://<username>:<password>@127.0.0.1:3128") // 認証情報を付加
    req, err := http.NewRequest("GET", "https://yahoo.co.jp", nil)
    if err != nil {
        log.Fatal(err)
    }
    c := &http.Client{
        Transport: &http.Transport{
            Proxy: http.ProxyURL(u),
        },
    }
    resp, err := c.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(resp.Status)
}
$ go run main.go
200 OK

おわり。

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