20200906のGoに関する記事は5件です。

Goでテストカバレッジを測定する

はじめに

Goは標準の機能としてテストカバレッジをだすことができます。
単純にテストのカバー率を算出するものから具体的にどこの部分のテストが足りないかを示してくれる機能もあるので今回はそれらを簡単にまとめたいと思います。

シンプルにテストカバレッジを測定する

$ go test -cover ./...
?       app_name/api    [no test files]
?       app_name/api/config     [no test files]
ok      app_name/api/handlers   0.421s  coverage: 100.0% of statements
?       app_name/api/responses  [no test files]

シンプルにテストカバレッジを測定するときはgo test -coverとすることでこのようにテストカバー率を出してくれます

具体的にどこがテストできてないかを表示する

# cover.outというファイルテストのカバー内容を吐き出す
$ go test -cover ./... -coverprofile=cover.out

# go toolを用いてcover.htmlを作成する
$ go tool cover -html=cover.out -o cover.html

# cover.htmlを開く
$ open cover.html

具体的にテストされていない場所を特定するときは上記のようにコマンドを打つことでテストできていないところを赤文字で表示させることができます。
スクリーンショット 2020-09-06 22.46.09.png

不要なファイルをカバレッジ計算から排除する

# cover.out.tmpというファイルテストのカバー内容を吐き出す
go test -cover ./... -coverprofile=cover.out.tmp

# 自動生成コードをカバレッジ対象から外し、カバレッジファイルを作成
cat cover.out.tmp | grep -v "**_mock.go" > cover.out

# 不必要なcover.out.tmpを削除
rm cover.out.tmp

# 以下先ほどと同じ
go tool cover -html=cover.out -o cover.html
open cover.html

テストカバー率を測定するときにmock用のコードなどが入ってしまうと適切なカバレッジを測定できないので、そのようなときは少しシェル芸します
上記の場合はコードをmockするときに_mock.goという接尾語をつけているのでその場合はカバレッジ測定のファイル(cover.out)から削除します。

最後に

goは標準でテストカバレッジツールがついているのはとてもいいですね!
真っ赤な状態からテストを書いてどんどん緑にしていく過程は結構楽しいです!!

以下個人的に使ってるmake fileコマンドです
独自のmockファイルとwireの自動生成ファイルを外すようにしています

Makefile
cover:
    go test -cover ./... -coverprofile=cover.out.tmp
    # 自動生成コードをカバレッジ対象から外し、カバレッジファイルを作成
    cat cover.out.tmp | grep -v "**_mock.go" | grep -v "wire_gen.go" > cover.out
    rm cover.out.tmp
    go tool cover -html=cover.out -o cover.html
    open cover.html

参考文献

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

goのテストの書き方【備忘録】

テスト実行

go test ./... -v

テストの流れ

各レイヤーのテストは基本的に下記の流れです。

  • mockを作成して
  • テスト対象のレシーバにセットして
  • テスト対象のメソッドを呼び出して
  • 結果を検証する

    t.Run("error", func(t *testing.T) {
        ctrl := gomock.NewController(t)
        GroupUsecase := mocks.NewMockIGroup(ctrl) -> mockを作る
        GroupUsecase.EXPECT().GetTopList().Return(nil, fmt.Errorf("test")) -> mockの挙動を決定
        h := &Group{
            GroupUsecase: GroupUsecase, -> セットして
        }
        e := echo.New()
        req := httptest.NewRequest(http.MethodPost, "/", nil)
        rec := httptest.NewRecorder()
        c := e.NewContext(req, rec)
        err := h.TopList(c) -> テスト対象メソッド呼出

        assert.Error(t, err) -> 結果を検証する
    })

gomock, mockgen

handler, usecaseは依存するinterfaceのmockをテスト中で実装していますが、mockはgomockを使って実装しています。

また、mockgenという定義したinterfaceからmockを作ってくれるツールも同梱しているのでmockはそれを使って生成しています。

$ go get github.com/golang/mock/gomock
$ go install github.com/golang/mock/mockgen

interfaceを追加、修正などしたらmockgen.shを実行してください。

sh mockgen.sh

すると、各レイヤーのmocks以下にmockが生成されます。

handler/mocks/
└── group.go

sqlmock

repositoryレイヤーはgorm.DBに依存しているのでgo-sqlmockを使ってgormが依存するdatabase/sqldatabase/mysqlをモックしてしまいます。

mockとは

各レイヤーはinterfaceを通じて依存しているので、interfaceを遵守するmockを代わりにセットすることで擬似的に各インスタンス呼び出しています。

mockを使うとモックDBなど用意する必要が無く原則ソースコード単体でテストを作ることができるようになります。

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

AWS Fargate, Github Actionsを利用したアプリケーションの開発から配布まで

入る前に

本記事で話そうとしてる主な内容は「こんなことを利用したら、こんなに簡単アプリの開発からDeployまでの環境が作れるんだ!」みたいの感じを紹介する記事であります。AWS、Dockerなどの技術の細かい設定や使い方、Goのアプリについて深い話は書いてないのでこの点認識してください。
各細かい内容については今後順次に書いて行く予定であります。

登場人物

  • AWS IAM
  • AWS ECR
  • AWS ECS
  • AWS ALB, EC2
  • AWS Parameter Store
  • Github Actions
  • Docker
  • Go

本記事では上記の内容似ていての設定方法などを細かくは案内しません。
躯体的な使い方についてはDocumentや、Hands Onなどを参考してください。
もし、Github Actionsについてお気になる方は自分が書いたこの記事を参考してください。

本日作業結果の予想図
image.png

作業の流れ

  1. Dockerを利用したContainer環境構築
  2. Goを利用したアプリ作成
  3. Local環境起動確認・テスト
  4. AWS設定
  5. Github Actions設定
  6. テスト

の流れになります。じゃぁ始めましょうか!

1. Dockerを利用したContainer環境構築

  • goが設定されてるAlphineLinuxを使う予定です。
  • TimeZone設定を行います。
  • ModuleをInstallします。
  • Buildします。
## go lang install
FROM golang:1.14.2-alpine3.11

## pakcage update & install
RUN apk add --update curl git pkgconfig curl-dev  &&  \
    go get github.com/cespare/reflex

## timezone update
RUN apk add tzdata
ENV TZ=Asia/Tokyo

## setting enviroment variables
ENV GO111MODULE "on"
ENV ENV "prod"

## source code copy
WORKDIR /go
COPY . /go/src

## module install
WORKDIR /go/src
RUN go install

## go build
RUN go build ./main.go

## container listen port
EXPOSE 8080

## command
## start
CMD ["./main"]

このようになります。

2. Goを利用したアプリ作成

  • go json-restこのModuleを利用する予定です。
    • この記事で扱うアプリの元もこのModuleの例文になります。
  • Go moduleを利用するため、環境変数GO111MODULE=onを設定します。
  • go mod init {{mod name}}を利用し go.modを作成します。
    • この記事でのmodはexmaple.com/mで設定してます。

参考コード

3. Local環境起動確認・テスト

  • docker build
  • postman, curlなどを利用してテスト
docker build -t qiita_test .
docker run -p 8080:8080 qiita_test:latest

postman でリクエストを投げて見ましょう!
image.png
おぉ! Accessログ出ますね やった!
Screenshot at Sep 06 13-05-18.png

4. AWS設定

  • IAM 設定

    • Github Actionが利用するDeployユーザーを作成
      • 権限はECR,ECS,Parameter Store権限を付与します。
      • 作成時 AccesToken、Keyを保存しましょう。 Screenshot at Sep 06 13-44-24.png
    • FargateのTaskが使うRoleを設定します。
      • ecsTaskExecutionRole この名がDefaultです。 Screenshot at Sep 06 13-51-39.png
  • Parameter Store 設定

    • Dockerを利用するときに、環境変数設定が必要な場合は利用します。
      • SystemManater > Parameter Storeでkey-valueで設定します。
  • Security Group 作成

    • 利用するポートを許可します。 この例では8080をOpenします。
  • ALB 作成

    • Public Subetを設定します。
    • Public DNSが使える状態もしくはHTTPS設定を利用して使えるDNS設定を行います。
  • ECR 作成

    • repositoryを作成します。
  • ECS 設定

    • task作成
      • Containerを設定するときに環境変数でvalueFromで、値を設定します。
    • cluster作成
    • service作成

5. Github, Github Actions設定

  • Trigger
    • push
  • Target Branch
    • master
  • doing
    • docker build
    • ECR push
    • task-definitionを利用したデプロイ作業
### main.yml
name: test workflow for qiita
on:
  push:                             # event trigger on push
    branches: master

jobs:
  build:                             # job id
    name: sjkim action               # job name
    runs-on: ubuntu-latest           # virtual os
    steps:
      - name: set up go 1.14
        uses: actions/setup-go@v1
        with:
          go-version: 1.14

      - name: Checkout branch go module directory
        uses: actions/checkout@v2

      - name: package install
        uses: actions/cache@v2
        with:
          path: ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: qiita-test
          IMAGE_TAG: ${{ github.sha }}
        run: |
           # Build a docker container and
           # push it to ECR so that it can
           # be deployed to ECS.
           docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
           docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
           echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

      - name: Fill in the new image ID in the Amazon ECS task definition
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: qiita-container
          image: ${{ steps.build-image.outputs.image }}

      - name: Deploy Amazon ECS task definition
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service: qiita-service
          cluster: qiita-test-cluster
          wait-for-service-stability: true

Github Actions marketplaceにあるECS Deployを元に作成してます。

Github Secrets Point

  • {{ secrets.AWS_SECRET_ACCESS_KEY }} などで利用可能
  • setting > secrets > new secrets image.png

task-definition.json

{
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "inferenceAccelerators": [],
  "containerDefinitions": [
    {
      "name": "qiita-container",
      "image": "***/qiita-test",
      "resourceRequirements": null,
      "essential": true,
      "portMappings": [
        {
          "hostPort": 8080,
          "containerPort": "8080",
          "protocol": "tcp"
        }
      ],
      "secrets": [
        {
          "name": "ENV",
          "valueFrom": "dev"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/qiita-test",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "qiita"
        }
      }
    }
  ],
  "volumes": [],
  "networkMode": "awsvpc",
  "memory": "512",
  "cpu": "256",
  "executionRoleArn": "****/ecsTaskExecutionRole",
  "family": "qiita-task",
  "taskRoleArn": "",
  "placementConstraints": []
}

task-definition point

  • ECS-task設定
  • container 情報
  • Log出力先設定
  • AWS Parameter Store設定(Docker起動時に設定する感じ)

6. テスト

じゃぁここまで来たらほぼ準備は終わってます。
実装をしてみましょう!

  • GithubのMasterブランチへPushしたら、Github Actionsが起動されます。
  • その流れによって、ECSにデプロイ作業が開始されます。
  • 作業が終わったら、AWS管理ページを確認し、EC2>ターゲットグループチェックします。
  • Public DNSや、利用してるDNSを利用してAPIが叩けるのか確認します!

結論

今回私も初めてこのような構成でアプリケーションの開発を行ってみました。
AWSの知識が浅かったのでかなり苦労した記憶が残ってますが、かなりいい方法の構成かな…と思ってるのでこれを利用してPJをどんどん拡張していきたいなと思います。
Firelensなどを利用したLog設定や、GithubActions、AWS SNSを利用したアラーム設定、などもどんどん入れてみたいなと思ってます。

おそらくこの記事である情報だけではアプリがそこまで簡単には作られないかもしれません、
でも、この記事の内容を元にし、皆さんのPJや、悩みに小さいHINTになったら嬉しいです。

本日案内したコードはここを参考してください。

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

Goroutineというのは?

はじめに

Goroutineというのは関数を同時に実行させる機能であります。
他の言語のスレッドより生成の方法が簡単です。その上スレッドよりもオーエスのリソースを少なく使いますので多くのGoroutineの生成が可能です。

簡単なコードを作成してみましょう!

Goroutine100個同時に実行し、ランダムな時間で待っているGo関数

qiita.go
import (
    "fmt"
    "math/rand"
    "time"
)
func randomNum(n int) {
    r := rand.Intn(100) //ランダム数値
    time.Sleep(time.Duration(r)) //ランダム時間待ち
    fmt.Println(n)
}

func main() {
    for i:=0; i<100; i++ {
        go randomNum(i)
    }
    fmt.Scanln()

→出力結果:ランダム数値が一個づつ改行で出ます。

closerをGoroutineで実行

qiita.go
func main(){
    runtime.GOMAXPROCS(1) // CPU一個
    s := "Hello, World"
    for i:=0; i<100; i++ {
        go func(n int) { //closerをGoroutineで実行
            fmt.Println(s, n) //sとパラメータでもらったnを出力
        }(i) //くり返した変数はパラメーターの方に渡す
    }
    fmt.Scanln()
}

*注意
上記の通り、Closerの中でGoroutineを実行する場合、for文により変わる変数は必ずパラメーターで渡します。

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

Goでjson

こんにちは

どうも、です。

今回はだいぶ軽めの記事になってます。内容は最近入門したGoについてです。
マジで何もわからん。

Goでjsonを扱う

Goでjsonを使いたい時は構造体を使います。必要な情報を持たせてあげるのです。PythonやJavaSctiptと違って少し手間ですが、こんな感じでやるみたいです。

Unmarshal

早速やってみます。
例えばこんな感じのjsonがくるとします。

var req string
req = `[
            {"name": "takurinton", "age": 20, "favorite": ["runnning", "baseball"]}, 
            {"name": "ryota", "age": 16, "favorite": ["fishing", "baseball"]}, 
            {"name": "hoge", "age": 26, "favorite": ["programming"]}, 
            {"name": "fuga", "age": 10, "favorite": ["study", "programming"]}
        ]`

それをGoで扱える形に変換したい時はこんな感じで書いてあげます。

main.go
package main

type Human struct {
    Name string 
    Age int 
    Favorite []string
}

func main() {
    var req string
    req = `[
            {"name": "takurinton", "age": 20, "favorite": ["runnning", "baseball"]}, 
            {"name": "ryota", "age": 16, "favorite": ["fishing", "baseball"]}, 
            {"name": "hoge", "age": 26, "favorite": ["programming"]}, 
            {"name": "fuga", "age": 10, "favorite": ["study", "programming"]}
        ]`

    bytes := []byte(req) // byte型に変換
    var human []Human
    if err := json.Unmarshal(bytes, &human); err != nil {
        log.Fatal(err)
    }
    for _, h := range human {
        fmt.Printf("name: %s, age: %d, favorite: %v\n ", h.Name, h.Age, h.Favorite)
    }
}
出力
name: takurinton, age: 20, favorite: [runnning baseball]
name: ryota, age: 16, favorite: [fishing baseball]
name: hoge, age: 26, favorite: [programming]
name: fuga, age: 10, favorite: [study programming]

GoにはUnmarshalというjsonをサポートしてくれる関数が準備されていて、これを利用することでよしなに変換してくれるわけです。
こやつはこんな感じの構造をしてます。第一引数はbyteのスライス、第二引数はインターフェイスを渡してあげます。上のプログラムでもしっかり値を渡すことができています。

func Unmarshal(data []byte, v interface{}) error

Marshal

逆もできます。

main.go
func main() {
    var req string
    req = `[
            {"name": "takurinton", "age": 20, "favorite": ["runnning", "baseball"]}, 
            {"name": "ryota", "age": 16, "favorite": ["fishing", "baseball"]}, 
            {"name": "hoge", "age": 26, "favorite": ["programming"]}, 
            {"name": "fuga", "age": 10, "favorite": ["study", "programming"]}
        ]`

    bytes := []byte(req) // byte型に変換
    var human []Human
    if err := json.Unmarshal(bytes, &human); err != nil {
        log.Fatal(err)
    }

    h, err := json.Marshal(human)
    if err != nil {
        log.Fatal(err)
    }
    res := string(h) // stringに変換
    fmt.Println(res)
}
出力
[{"Name":"takurinton","Age":20,"Favorite":["runnning","baseball"]},{"Name":"ryota","Age":16,"Favorite":["fishing","baseball"]},{"Name":"hoge","Age":26,"Favorite":["programming"]},{"Name":"fuga","Age":10,"Favorite":["study","programming"]}]

Marshalの中身はこんな感じです。
値はさっきUnmarshalで使ったものをそのまま使用しました。
戻り値が[]byteとのことなのでいい感じにするためにstringに変換しました。

func Marshal(v interface{}) ([]byte, error)

いい感じに変換されました。

これどうやら遅いらしい

らしいです。

easyjson

そこで出てくるのが、easyjsonというやつです。詳しくは自分で調べてください。
go getで持ってきます。

go get -u github.com/mailru/easyjson/...

これを使うと構造体ごとにコードを自動生成してReflectionなしで高速で先ほどのMarshalやUnmarshalができるようになります。

まずは適当なファイルを作成します。今回はeasy.goというファイルを作成しました。

easy.go
package main

type Human struct {
    Name     string
    Age      int
    Favorite []string
}

これを作成したら、ターミナルで以下のコマンドを叩きます。

easyjson -all easy.go

そうすると、同じディレクトリの中に新しくeasy_easyjson.goというファイルが自動で作られます。
中身はこんな感じになってます。

easy_easyjson.go
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.

package main

import (
    json "encoding/json"
    easyjson "github.com/mailru/easyjson"
    jlexer "github.com/mailru/easyjson/jlexer"
    jwriter "github.com/mailru/easyjson/jwriter"
)

// suppress unused package warning
var (
    _ *json.RawMessage
    _ *jlexer.Lexer
    _ *jwriter.Writer
    _ easyjson.Marshaler
)

func easyjson97766e5aDecodeJsonPracticeEasy(in *jlexer.Lexer, out *Human) {
    isTopLevel := in.IsStart()
    if in.IsNull() {
        if isTopLevel {
            in.Consumed()
        }
        in.Skip()
        return
    }
    in.Delim('{')
    for !in.IsDelim('}') {
        key := in.UnsafeFieldName(false)
        in.WantColon()
        if in.IsNull() {
            in.Skip()
            in.WantComma()
            continue
        }
        switch key {
        case "Name":
            out.Name = string(in.String())
        case "Age":
            out.Age = int(in.Int())
        case "Favorite":
            if in.IsNull() {
                in.Skip()
                out.Favorite = nil
            } else {
                in.Delim('[')
                if out.Favorite == nil {
                    if !in.IsDelim(']') {
                        out.Favorite = make([]string, 0, 4)
                    } else {
                        out.Favorite = []string{}
                    }
                } else {
                    out.Favorite = (out.Favorite)[:0]
                }
                for !in.IsDelim(']') {
                    var v1 string
                    v1 = string(in.String())
                    out.Favorite = append(out.Favorite, v1)
                    in.WantComma()
                }
                in.Delim(']')
            }
        default:
            in.SkipRecursive()
        }
        in.WantComma()
    }
    in.Delim('}')
    if isTopLevel {
        in.Consumed()
    }
}
func easyjson97766e5aEncodeJsonPracticeEasy(out *jwriter.Writer, in Human) {
    out.RawByte('{')
    first := true
    _ = first
    {
        const prefix string = ",\"Name\":"
        out.RawString(prefix[1:])
        out.String(string(in.Name))
    }
    {
        const prefix string = ",\"Age\":"
        out.RawString(prefix)
        out.Int(int(in.Age))
    }
    {
        const prefix string = ",\"Favorite\":"
        out.RawString(prefix)
        if in.Favorite == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
            out.RawString("null")
        } else {
            out.RawByte('[')
            for v2, v3 := range in.Favorite {
                if v2 > 0 {
                    out.RawByte(',')
                }
                out.String(string(v3))
            }
            out.RawByte(']')
        }
    }
    out.RawByte('}')
}

// MarshalJSON supports json.Marshaler interface
func (v Human) MarshalJSON() ([]byte, error) {
    w := jwriter.Writer{}
    easyjson97766e5aEncodeJsonPracticeEasy(&w, v)
    return w.Buffer.BuildBytes(), w.Error
}

// MarshalEasyJSON supports easyjson.Marshaler interface
func (v Human) MarshalEasyJSON(w *jwriter.Writer) {
    easyjson97766e5aEncodeJsonPracticeEasy(w, v)
}

// UnmarshalJSON supports json.Unmarshaler interface
func (v *Human) UnmarshalJSON(data []byte) error {
    r := jlexer.Lexer{Data: data}
    easyjson97766e5aDecodeJsonPracticeEasy(&r, v)
    return r.Error()
}

// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Human) UnmarshalEasyJSON(l *jlexer.Lexer) {
    easyjson97766e5aDecodeJsonPracticeEasy(l, v)
}

なんとも便利な〜
これは先ほどのMarshalやUnmarshalと同じように使うことができます。

まずはMarshalから

main.go
package main

import (
    "fmt"
    "json_practice/easy"
    "log"

    "github.com/mailru/easyjson"
)

func main() {
    req := Human{
        Name: "takurinton",
        Age:  20,
        Favorite: []string{
            "running",
            "baseball",
        },
    }

    h, err := easyjson.Marshal(req)
    if err != nil {
        log.Fatal(err)
    }
    res := string(h)
    fmt.Println(res)
}
出力
{"Name":"takurinton","Age":20,"Favorite":["running","baseball"]}

こんな感じでうまく変換することができます。同じように使用できるのはいいですねえ。easy.goを構造体ではなくスライスの中に構造体入れるみたいな感じにしてあげればそれもまたいい感じに変換してくれます。

Unmarshalも上と同様に同じ値を使って実装してみたいと思います。

main.go
package main

import (
    "fmt"
    "json_practice/easy"
    "log"

    "github.com/mailru/easyjson"
)

func main() {
    req := Human{
        Name: "takurinton",
        Age:  20,
        Favorite: []string{
            "running",
            "baseball",
        },
    }

    h, err := easyjson.Marshal(req)
    if err != nil {
        log.Fatal(err)
    }
    human := Human{}
    if err := easyjson.Unmarshal(h, &human); err != nil {
        log.Fatal(err)
    }

    fmt.Println(human)
}
出力
{takurinton 20 [running baseball]}

こんな感じで出力されます。
結構簡単に実装できますね。

まとめ

今回は軽く触れただけですが、easyjsonは標準のやつより速度が出るみたいなので今度実験でもしときます。
Goでjsonを触るざっくりとしたイメージは掴めたと思うので、これを利用してプログラミング頑張って行きたいと思います。
では、ここらへんで失礼します。

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