コンテンツにスキップ

go test を実行すると自動的に go vet が走る

概要

go test を実行すると、裏で自動的に go vet が実行されていることを知らなかった、という #golangtokyo での発見の共有。

詳細

go vet とは

Go の静的解析ツール。コンパイルは通るが、バグになりそうな疑わしいコードを検出する。

go vet ./...

検出する問題の例:

// Printf フォーマット文字列の型不一致(コンパイルは通る、でも bug)
fmt.Printf("%d", "hello")  // vet が警告: %d expects integer, got string

// unreachable なコード
func f() int {
    return 1
    return 2  // vet が警告: unreachable code
}

// sync.Mutex のコピー(ポインタ渡しすべきところを値渡し)
type S struct{ mu sync.Mutex }
func use(s S) {}  // vet が警告: passes lock by value

go test との関係

# これを実行すると...
go test ./...

# 内部でこれが自動的に走っている
go vet ./...   # 先にvetを実行
go test ./...  # vetが通ったらtestを実行

go test が失敗するケース: 1. go vet で問題が検出された場合(テストコードすら実行されない) 2. テストが失敗した場合

実際の挙動確認

// main_test.go
package main

import (
    "fmt"
    "testing"
)

func TestBad(t *testing.T) {
    // go vet で検出される問題
    fmt.Printf("%d", "not an int")
    t.Log("test")
}
$ go test .
# main [main.test]
./main_test.go:10:17: fmt.Printf format %d has arg "not an int" of wrong type string
FAIL    main [build failed]

vet が通らないとテスト自体が実行されない。

vet を無効化する方法(非推奨)

# vetをスキップしてテストだけ実行(通常は使わない)
go test -vet=off ./...

通常は無効化せず、vet の警告を修正する。

CI でのベストプラクティス

# GitHub Actions
- name: Test
  run: go test ./...
  # go test は go vet を含むので別途 vet を実行する必要はない
  # ただし明示的に実行したい場合
- name: Vet (explicit)
  run: go vet ./...

CI で go test を実行していれば go vet も同時に実行されているので二重実行は不要。ただし明示的に書くことでチームメンバーへの周知になる。

なぜ重要か / いつ使うか

  • Go のテスト環境をセットアップするとき
  • CI パイプラインを構築するとき
  • go test が「ビルドが通るのに落ちる」という状況で原因調査するとき