- 投稿日:2020-09-06T23:36:55+09:00
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具体的にテストされていない場所を特定するときは上記のようにコマンドを打つことでテストできていないところを赤文字で表示させることができます。
不要なファイルをカバレッジ計算から排除する
# 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の自動生成ファイルを外すようにしていますMakefilecover: 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参考文献
- 公式ドキュメント(https://blog.golang.org/cover)
- Go でコードカバレッジを取得する(https://qiita.com/kkohtaka/items/965fe08821cda8c9da8a)
- 投稿日:2020-09-06T18:06:08+09:00
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/mockgeninterfaceを追加、修正などしたら
mockgen.shを実行してください。sh mockgen.shすると、各レイヤーの
mocks以下にmockが生成されます。handler/mocks/ └── group.gosqlmock
repositoryレイヤーはgorm.DBに依存しているのでgo-sqlmockを使ってgormが依存するdatabase/sqlのdatabase/mysqlをモックしてしまいます。mockとは
各レイヤーはinterfaceを通じて依存しているので、interfaceを遵守するmockを代わりにセットすることで擬似的に各インスタンス呼び出しています。
mockを使うとモックDBなど用意する必要が無く原則ソースコード単体でテストを作ることができるようになります。
- 投稿日:2020-09-06T15:40:31+09:00
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についてお気になる方は自分が書いたこの記事を参考してください。作業の流れ
- Dockerを利用したContainer環境構築
- Goを利用したアプリ作成
- Local環境起動確認・テスト
- AWS設定
- Github Actions設定
- テスト
の流れになります。じゃぁ始めましょうか!
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:latestpostman でリクエストを投げて見ましょう!
おぉ! Accessログ出ますね やった!
4. AWS設定
IAM 設定
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: trueGithub Actions marketplaceにあるECS Deployを元に作成してます。
Github Secrets Point
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になったら嬉しいです。本日案内したコードはここを参考してください。
- 投稿日:2020-09-06T09:14:36+09:00
Goroutineというのは?
はじめに
Goroutineというのは関数を同時に実行させる機能であります。
他の言語のスレッドより生成の方法が簡単です。その上スレッドよりもオーエスのリソースを少なく使いますので多くのGoroutineの生成が可能です。簡単なコードを作成してみましょう!
Goroutine100個同時に実行し、ランダムな時間で待っているGo関数
qiita.goimport ( "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.gofunc 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文により変わる変数は必ずパラメーターで渡します。
- 投稿日:2020-09-06T00:59:00+09:00
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.gopackage 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{}) errorMarshal
逆もできます。
main.gofunc 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.gopackage 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.gopackage 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.gopackage 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を触るざっくりとしたイメージは掴めたと思うので、これを利用してプログラミング頑張って行きたいと思います。
では、ここらへんで失礼します。






