🫧

gotestsumを使いこなしてGoのテスト体験を向上したい

2024/08/31に公開

はじめに

突然ですがGoのテスト結果って見づらくないでしょうか?

Goのテスト実行例
% go test -v ./...
?   	gotestsum-demo/fizzbuzz	[no test files]
=== RUN   TestAdd
=== PAUSE TestAdd
=== CONT  TestAdd
=== RUN   TestAdd/1+0
=== PAUSE TestAdd/1+0
=== RUN   TestAdd/10+10
=== PAUSE TestAdd/10+10
=== RUN   TestAdd/1+1
=== PAUSE TestAdd/1+1
=== RUN   TestAdd/2+1
=== PAUSE TestAdd/2+1
=== RUN   TestAdd/1-1
=== PAUSE TestAdd/1-1
=== RUN   TestAdd/1+2
=== PAUSE TestAdd/1+2
=== CONT  TestAdd/1+0
=== CONT  TestAdd/1-1
=== CONT  TestAdd/2+1
=== CONT  TestAdd/10+10
=== CONT  TestAdd/1+2
=== CONT  TestAdd/1+1
--- PASS: TestAdd (0.00s)
    --- PASS: TestAdd/1+0 (0.00s)
    --- PASS: TestAdd/2+1 (0.00s)
    --- PASS: TestAdd/10+10 (0.00s)
    --- PASS: TestAdd/1+2 (0.00s)
    --- PASS: TestAdd/1-1 (0.00s)
    --- PASS: TestAdd/1+1 (0.00s)
PASS
ok  	gotestsum-demo/calculate	0.400s
=== RUN   TestHello
=== PAUSE TestHello
=== RUN   TestHello2
--- PASS: TestHello2 (0.00s)
=== CONT  TestHello
=== RUN   TestHello/Hello,_gotestsum!!
=== PAUSE TestHello/Hello,_gotestsum!!
=== RUN   TestHello/Hello,_World!!
=== PAUSE TestHello/Hello,_World!!
=== RUN   TestHello/Hello,_Go!!
=== PAUSE TestHello/Hello,_Go!!
=== CONT  TestHello/Hello,_gotestsum!!
=== CONT  TestHello/Hello,_Go!!
=== CONT  TestHello/Hello,_World!!
--- PASS: TestHello (0.00s)
    --- PASS: TestHello/Hello,_gotestsum!! (0.00s)
    --- PASS: TestHello/Hello,_Go!! (0.00s)
    --- PASS: TestHello/Hello,_World!! (0.00s)
PASS
ok  	gotestsum-demo/hello	0.206s

-vオプションをつけたりすると出力がやたら多くなるうえに全て白文字なので成功したのか失敗したのかがパッと見でわかりづらい気がします。

Goはリッチな開発者体験を提供するよりもパフォーマンスや安全性などを重視する文化のようなものがあると思っているのであんまり気にしたことなかったのですが最近他の言語でテストを書いたりするとテスト結果が見やすいなーと思うことがありました。

さすがに色付きでテスト結果を表示したいなと思い調べたところgotestsumというツールが便利そうだったのでいろいろ触った備忘録です。

(正直VSCodeのようなエディタからテストを実行していたりするとエディタ側で見やすく表示してくれたりするのであんまりターミナルからテストを実行することも多くないんですが...)

成果物はこちら

https://github.com/JY8752/gotestsum-demo

この記事で紹介すること

  • gotestsumを使ったローカルでのテスト実行のあれこれ
  • gotestsumを使ったGitHub Actionsでのレポート表示の方法
  • gotestsumとoctocovを使ったGitHub Actionsでのテストカバレッジの表示方法

対象読者

  • Goのテスト結果をもう少しリッチにしたいと実は思っている人
  • Goのテストは白文字出力で十分、むしろ白文字が良いと思ってる人
  • GitHub ActionsのようなCI環境でGoのテスト結果をレポートとして表示させたい人
  • GitHub ActionsのようなCI環境でPRのコメントなどにテストカバレッジを表示させたい人

gotestsumについて

gotestsumはgo test -jsonを実行し、その結果をフォーマットして表示するCLIツールです。ローカルでの実行にもCI環境での実行でも使用できるように設計されており、以下のような著名なプロジェクトでも利用されています。

  • kubernetes
  • hashicorp/vault
  • prometheus
  • minikube
  • containerd
  • docker/cli
    ...etc

インストールはgo installでできます。

go install gotest.tools/gotestsum@latest

% gotestsum --version
> gotestsum version dev

gotestsumを使ったテスト実行

とりあえずテストを実行できるように簡単な関数とテストを用意します。

go mod gotestsum-demo
calculate/calculate.go
package calculate

func Add(x, y int) int {
	return x + y
}
calculate/calculate_test.go
package calculate_test

import (
	"gotestsum-demo/calculate"
	"testing"
)

func TestAdd(t *testing.T) {
	t.Parallel()
	tests := map[string]struct {
		x, y, expected int
	}{
		"1+1":   {x: 1, y: 1, expected: 2},
		"2+1":   {x: 2, y: 1, expected: 3},
		"1-1":   {x: 1, y: -1, expected: 0},
		"1+2":   {x: 1, y: 2, expected: 3},
		"1+0":   {x: 1, y: 0, expected: 1},
		"10+10": {x: 10, y: 10, expected: 20},
	}

	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			if result := calculate.Add(tt.x, tt.y); result != tt.expected {
				t.Errorf("expected %d, but %d\n", tt.expected, result)
			}
		})
	}
}

ではgotestsumを使ってテストを実行してみましょう。gotestsumのみで実行するとgo test ./...と同じ範囲で実行されます。--の後に通常のgo testの後に続くパスやオプションを指定して実行することも可能です。

% gotestsum
✓  calculate (275ms)

DONE 7 tests in 0.893s

フォーマットを変更して出力する

gotestsumは以下のフォーマットをサポートしています。

  • dots ドットで結果を出力
  • dots-v2 実験的ドット出力
  • pkgname (default) パッケージごとに結果を出力
  • pkgname-and-test-fails パッケージごとに失敗したテストも出力
  • testname テスト名ごとに結果を出力
  • testdox gotestdoxのフォーマットを使用して出力
  • github-actions GitHub ActionsのLog groupingとともにtestnameのフォーマットで出力
  • standard-quiet go testと同等の出力
  • standard-verbose go test -vと同等の出力

ここではいくつかのフォーマットを紹介します。

dots

testname

testdox

個人的にはテーブル駆動テストで実行するサブテストの結果まで見たいと思っているのでtestnametestdoxのフォーマットが見やすくていいなと思っています。

ちなみに、pkgnametestdoxはアイコンを変更することもできます。

    default                  the original unicode (✓, ∅, ✖)
    hivis                    higher visibility unicode (✅, ➖, ❌)
    text                     simple text characters (PASS, SKIP, FAIL)
    codicons                 requires a font from https://www.nerdfonts.com/ (  )
    octicons                 requires a font from https://www.nerdfonts.com/ (  )
    emoticons                requires a font from https://www.nerdfonts.com/ (󰇵 󰇶 󰇸)

codiconsocticonsemoticonsNerd Fontsが必要になります。

最も実行速度が遅いテストを表示する

gotestsum--post-run-commandオプションを指定することでテスト実行後に指定の処理を実行することができます。また、--jsonfile--junitfileを指定することでテスト結果をレポートとしてファイルに出力することが可能です。

これらのオプションを組み合わせるとテスト結果の出力に実行速度が最も遅いテストを表示することが可能です。以下のコマンドは実行速度が遅いテストを上から5つ表示するコマンドです。

% gotestsum \
  --jsonfile tmp.json.log \
  --post-run-command "bash -c '
    echo; echo Slowest tests;
    gotestsum tool slowest --num 5 --jsonfile tmp.json.log'"
✓  calculate (10.241s)

DONE 10 tests in 10.814s

Slowest tests
gotestsum-demo/calculate Test10S 10s
gotestsum-demo/calculate Test5S 5s
gotestsum-demo/calculate Test3S 3s
gotestsum-demo/calculate TestAdd/10+10 0s
gotestsum-demo/calculate TestAdd/1+0 0s

テストの実行完了を通知する

上記で紹介した--post-run-commandオプションを使用することでテストの完了を通知することも可能です。Macの場合、デスクトップ通知にterminal-notifierを使用します。

brew install terminal-notifier

terminal-notifier -version
> terminal-notifier 2.0.0.

次に以下のexampleの通知プログラムをインストールします。

go install gotest.tools/gotestsum/contrib/notify

以下のコマンドのようにテスト実行後に実行するコマンドとしてnotifyを指定するとテスト完了後にデスクトップ通知を飛ばすことができます。

gotestsum --post-run-command notify

ソースコードを監視してテストを実行する

--watchオプションを使用することでカレントディレクトリ配下の全てのgoファイルを関監視しファイルが変更されるたびにテストを実行することが可能です。

gotestsum --format testdox --watch

ここまでのまとめ

ほかにも失敗したテストを再度実行するようなオプションがあったりと便利な機能があるgotestsumですが最低限ローカルで実行したテストの結果出力を見やすくするだけでも十分導入価値があるように感じます。

go testの代わりに使う場合は毎回formatを指定したりするのは面倒なので以下のようなエイリアス設定を私はしています。

alias gotest="gotestsum --format testdox"
alias gotestv="gotestsum --format standard-verbose"
alias gotestw="gotestsum --format testdox --watch"
gotest -- -count=1 ./...

次にCI環境でのgotestsumの利用について見ていきましょう。

GitHub Actionsでgotestsumを使う

今回はGitHub Actionsでgotestsumを使っていきたいと思います。gotesetsumを使うためにGitHub Actionsのワークフローのステップ内で以下のようにしてインストールしていきます。毎回インストールするのも効率が悪いのでactions/cacheを使ってキャッシュしています。

- name: Cache gotestsum
  id: gotestsum-cache
  uses: actions/cache@v4
  with:
    path: ${{ env.GOTESTSUM_BIN}}
    key: ${{ runner.os }}-go-gotestsum

- name: Install gotestsum
  if: ${{ steps.gotestsum-cache.outputs.cache-hit != 'true' }}
  run: GOBIN="${{ env.GOTESTSUM_BIN }}" go install gotest.tools/gotestsum@latest

インストールできたら以下のようにしてワークフロー内でテストを実行できます。

- name: Run tests and generate JUnit report, test coverage
  run: "${{ env.GOTESTSUM_BIN }}/gotestsum --format testdox

テストレポートを表示する

ここまででCI環境でgotestsumを使用してテストを実行することができるようになりましたがただ実行するだけであればわざわざgotestsumをインストールする意味はあまりありません。

既に説明しましたがgotestsumにはJUnit形式のレポートを出力することができるため、GitHub Actionsでテストレポートを見れるよにしていきましょう。

  - name: Cache gotestsum
    id: gotestsum-cache
    uses: actions/cache@v4
    with:
      path: ${{ env.GOTESTSUM_BIN}}
      key: ${{ runner.os }}-go-gotestsum

  - name: Install gotestsum
    if: ${{ steps.gotestsum-cache.outputs.cache-hit != 'true' }}
    run: GOBIN="${{ env.GOTESTSUM_BIN }}" go install gotest.tools/gotestsum@latest

  - name: Run tests and generate JUnit report, test coverage
- run: "${{ env.GOTESTSUM_BIN }}/gotestsum --format testdox
+    run: "${{ env.GOTESTSUM_BIN }}/gotestsum --junitfile report.xml --format testdox"

+  - name: Test Report Summary
+    if: success() || failure()
+    uses: dorny/test-reporter@v1
+    with:
+      name: Tests
+      path: "*.xml"
+          reporter: java-junit

出力したレポートファイルはdorny/test-reporterを使用してGitHub Actionsから見れるようにしました。類似のアクションとしてpublish-unit-test-result-actionというものもあるようなので好きなものを使ってください。

CIのワークフローが成功すると以下のようなレポートが作成されます。

テストカバレッジをPRのコメントに書き込む

せっかくなのでGitHub Actionsを使ってテストのカバレッジを見れるようにしてみましょう。よくあるケースとしてPRのコメントにカバレッジレポートを追加するというものがあるようなのでそれをgotestsumを使ってやっていきます。

と言ってもgotestsumの機能というよりはgo testの機能としてカバレッジを出力することができるためそれを使ってカバレッジを出力します。

  - name: Run tests and generate JUnit report, test coverage
-    run: "${{ env.GOTESTSUM_BIN }}/gotestsum --junitfile report.xml --format testdox"
+    run: "${{ env.GOTESTSUM_BIN }}/gotestsum --junitfile report.xml --format testdox --  -cover -coverprofile=coverage.out ./..."

これでcoverage.outというファイルにカバレッジを出力することができました。

あとは出力したファイルを使い良い感じにPRのコメントに追加できれば良く、いろいろ調べたのですが最終的にk1LoW/octocov-actionを使用させていただきました。

似たようなactionは他にもあったのです1番シンプルで使いやすそうだったため採用させていただきました。

octocovをGitHub Actionsで使うにはまず、以下のような設定ファイルを作成します。

.octocov.yml
coverage:
  badge:
    path: docs/coverage.svg
push:
  if: is_default_branch
codeToTestRatio:
  code:
    - '**/*.go'
    - '!**/*_test.go'
  test:
    - '**/*_test.go'
  badge:
    path: docs/ratio.svg
testExecutionTime:
  badge:
    path: docs/time.svg
diff:
  datastores:
    - artifact://${GITHUB_REPOSITORY}
comment:
  if: is_pull_request
report:
  datastores:
    - artifact://${GITHUB_REPOSITORY}
summary:
  if: true

設定ファイルが書けたらワークフローに以下の1文を追記します。

  - uses: k1LoW/octocov-action@v1

これでワークフローが実行されると以下のようなコメントをPRに記載することができます。

octocovはカバレッジとテストの実行時間とコードに対するテスト比率の3つを出力してくれますし、前回の結果をもとに差分を出力してくれたりもします。1番いいなと思ったのはそれぞれのbadgeを作成してくれてpushまでしてくれるのでREADMEなどに記載するのがとても簡単です。

最終的なワークフローは以下のようになりました。

最終的なワークフロー
.github/workflows/test.yaml
name: Go test with report

on:
  push:
    branches:
      - main
  pull_request:

defaults:
  run:
    shell: bash

jobs:
  test-with-report:
    runs-on: ubuntu-latest

    permissions:
      contents: write
      actions: read
      checks: write
      pull-requests: write

    env:
      GOTESTSUM_BIN: "${{ github.workspace }}/.tmp/gotestsum/bin"

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version-file: "go.mod"

      - name: Cache gotestsum
        id: gotestsum-cache
        uses: actions/cache@v4
        with:
          path: ${{ env.GOTESTSUM_BIN}}
          key: ${{ runner.os }}-go-gotestsum

      - name: Install gotestsum
        if: ${{ steps.gotestsum-cache.outputs.cache-hit != 'true' }}
        run: GOBIN="${{ env.GOTESTSUM_BIN }}" go install gotest.tools/gotestsum@latest

      - name: Run tests and generate JUnit report, test coverage
        run: "${{ env.GOTESTSUM_BIN }}/gotestsum --junitfile report.xml --format testdox --  -cover -coverprofile=coverage.out ./..."

      - name: Upload test report and coverage
        uses: actions/upload-artifact@v4
        with:
          name: junit-test-report-and-coverage
          path: |
            report.xml
            coverage.out

      - name: Test Report Summary
        if: success() || failure()
        uses: dorny/test-reporter@v1
        with:
          name: Tests
          path: "*.xml"
          reporter: java-junit

      - uses: k1LoW/octocov-action@v1

おわりに

本記事では以下のことを紹介しました。

  • gotestsumを使ったローカルテストの実行結果の表示をリッチにする方法
  • gotestsumを使いテスト実行後に処理を挟んだり、ソースコードの監視をしてテストの実行などをする方法
  • GitHub Actionsでテストレポートを表示する方法
  • GitHub ActionsでPRのコメントにテストカバレッジを追加する方法

本記事を書くモチベーションとしてはローカル環境でのテストの実行結果を見やすくしたいというものでしたがgotestsumを使うことでその目的は達成することができました。

もし、Goのテストに満足していない方がいましたらgotestsumを試してみてください!

今回は以上です🐼

(おまけ) 本当にテスト結果を見やすくする必要はあるのか?

調べてる中でgotestsum以外にrichgoというツールも見つけました。こちらはテスト結果の出力を解析して見た目を調整するような実装をしており、シンプルにGoのテスト結果をリッチに出力するためのツールなようです。

始めはこちらも試してみようかなと思ったのですがリポジトリを見てみるとREADMEに以下のような記載がありました。(DeepLで翻訳しています。翻訳がおかしいところは少し修正しています。)

何年か前から、richgoは使っていない。 今となっては、テスト出力の見た目をちょっといじったくらいでは、あまり効果を感じない。 また、richgoはgo testの標準出力を解析して調整するという稚拙な方法である。 というわけで、richgoは使わないで、純粋なgo testに慣れて、出力からエラーを見つける能力を鍛え、必要であれば公式go testの改良に貢献することをお勧めします。 私が純粋なGoを信頼しすぎていると思われるかもしれませんが、これは私の正直な気持ちです。

Goのテスト結果の出力をリッチにさせたいと思いOSSを作り、使い続けてきた結果、OSS作者自身効果を感じなくなったため使わなくなったとのことです。

Goのテスト結果をリッチにしたほうが良いのか。素のテスト結果に慣れたほうがいいのか。こちらに関しては意見が分かれそうです。

個人的にはテストは楽しく書くという持論があるのと見た目から入るタイプなのでテストの結果表示なんかも綺麗にフォーマットされて出力された方がテンションが上がるなと思っています。

なのでgotestsumのようなツールを導入することでテストを楽しく書けるようになるなら導入すればいいのではないかなぁと思ったりします。

(しかし、自分で作ってスター数もそれなりについているOSSをもう使わなくなったって言えるのすごいなぁ)

GitHubで編集を提案

Discussion