Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15431] ファイルの概要

このコミットは、Go言語の静的解析ツールである cmd/vet のテストコードの管理方法に関する重要な変更を導入しています。具体的には、vet ツール自体のソースコード内に埋め込まれていたテストコードを、独立したファイル群に分離し、ビルドタグを用いて通常のバイナリビルドから除外するように変更しています。これにより、vet ツールのバイナリサイズを削減し、テストコードとプロダクションコードの分離を明確にしています。

コミット

commit 31444a796a70530238b23d7603f85bf8d524d5da
Author: Rob Pike <r@golang.org>
Date:   Mon Feb 25 16:25:34 2013 -0800

    cmd/vet: move the tests into separate files
    Then mark them with a build tag so they're not compiled into the binary.
    They are called test_*.go rather than *_test.go because they are not
    for go test. Use make test to test the command.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/7377052

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/31444a796a70530238b23d7603f85bf8d524d5da

元コミット内容

cmd/vet: テストを別ファイルに移動。 その後、ビルドタグでマークし、バイナリにコンパイルされないようにする。 これらは go test 用ではないため、*_test.go ではなく test_*.go と命名されている。 コマンドをテストするには make test を使用する。

変更の背景

Go言語の cmd/vet ツールは、Goプログラムの潜在的なバグや疑わしい構成を検出するための静的解析ツールです。このツールは、開発プロセスにおいてコード品質を向上させるために非常に重要です。

このコミット以前は、vet ツールのテストコードが、vet ツール自体のソースコードファイル(例: atomic.go, print.go など)内に直接記述されていました。これは、開発初期段階では便利かもしれませんが、いくつかの問題を引き起こします。

  1. バイナリサイズの増大: テストコードは通常、最終的なプロダクションバイナリには含まれるべきではありません。ソースコード内にテストが混在していると、vet ツールのバイナリサイズが不必要に大きくなります。
  2. コードの分離の欠如: プロダクションコードとテストコードが同じファイルに混在していると、コードの可読性や保守性が低下します。コードベースが成長するにつれて、この問題はより顕著になります。
  3. ビルドプロセスの複雑化: テストコードをビルドから除外するために、特別なビルド設定や条件付きコンパイルが必要になる場合があります。

このコミットの背景には、これらの問題を解決し、cmd/vet の開発とメンテナンスをより効率的にするという目的があります。テストコードを独立したファイルに移動し、ビルドタグを使用することで、これらの問題に対処しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念とツールの知識が必要です。

  1. cmd/vet: Go言語の標準ツールチェーンに含まれる静的解析ツールです。go vet コマンドとして実行され、Goソースコードを分析して、一般的なプログラミングエラー(例: Printf フォーマット文字列と引数の不一致、構造体タグの誤り、アトミック操作の誤用、range ループ変数のクロージャでの誤用など)を検出します。これらのチェックは、コンパイル時には検出されないが、実行時に問題を引き起こす可能性のあるコードパターンを特定するのに役立ちます。

  2. Goのビルドタグ (Build Tags): Goのビルドタグは、特定の条件に基づいてソースファイルを含めたり除外したりするためのメカニズムです。Goソースファイルの先頭に // +build tagname の形式でコメントとして記述されます。ビルドコマンド(go build, go run, go test など)に -tags フラグを指定することで、特定のタグを持つファイルのみをコンパイル対象に含めることができます。 例えば、// +build debug と書かれたファイルは、go build -tags debug と実行された場合にのみコンパイルされます。複数のタグをスペースで区切って指定したり、! を使ってタグを除外したりすることも可能です(例: // +build linux,amd64 !debug)。

  3. go test とテストファイルの命名規則: Go言語には、組み込みのテストフレームワークがあり、go test コマンドで実行されます。go test は、ファイル名が _test.go で終わるファイルを自動的にテストファイルとして認識し、その中の TestXxxBenchmarkXxxExampleXxx という関数を実行します。 このコミットでは、test_*.go という命名規則が採用されています。これは、これらのファイルが go test コマンドによって直接実行される通常のユニットテストではないことを示しています。代わりに、vet ツール自体の動作を検証するための特別なテストであり、Makefile を介して実行されることを意図しています。

  4. Makefile: Makefile は、ソフトウェアのビルドプロセスを自動化するためのツールである make コマンドによって使用される設定ファイルです。このファイルには、ソースコードのコンパイル、テストの実行、パッケージの作成など、プロジェクトのビルドに関連するタスクを定義するルールが記述されます。このコミットでは、Makefile が更新され、vet ツールのテストを実行するための新しいルールが追加されています。

技術的詳細

このコミットの技術的な核心は、cmd/vet のテストコードを、Goのビルドシステムと Makefile を利用して、プロダクションコードから分離し、効率的に管理することにあります。

  1. テストコードの分離: atomic.go, buildtag.go, method.go, print.go, print_unsafe.go, rangeloop.go, structtag.go, taglit.go といった既存の vet のソースファイルから、テスト目的で記述されていた関数や型定義(例: BadAtomicAssignmentUsedInTests, BadFunctionUsedInTests, errorTest1 など)が削除されました。 これらの削除されたテストコードは、それぞれ対応する新しいファイル(test_atomic.go, test_buildtag.go, test_method.go, test_print.go, test_rangeloop.go, test_structtag.go, test_taglit.go)に移動されました。これにより、vet ツールの主要なロジックを含むファイルは、テストコードを含まないクリーンな状態になりました。

  2. ビルドタグ vet_test の導入: 新しく作成されたすべてのテストファイル(test_*.go)の先頭には、// +build vet_test というビルドタグが追加されました。このタグは、これらのファイルが通常の go build コマンドではコンパイルされないことを意味します。 以前は print_unsafe.go のように // +build unsafe タグを使用していたファイルもありましたが、このコミットではその内容も test_print.go に移動され、vet_test タグの下に統合されました。これにより、vet のテストに関連するすべてのファイルが単一のビルドタグで管理されるようになります。

  3. Makefile の更新: src/cmd/vet/Makefile が変更され、test ターゲットのビルドコマンドが更新されました。 変更前: go build -tags unsafe 変更後: go build -tags vet_test この変更により、make test コマンドが実行されると、go buildvet_test タグを持つファイル(つまり、新しく分離されたテストファイル群)をコンパイル対象に含めるようになります。これにより、vet ツール自体をテストするための特別なバイナリが生成され、そのバイナリに対してテストが実行される仕組みが確立されました。

  4. テストファイルの命名規則 test_*.go: コミットメッセージで明示されているように、これらのテストファイルは *_test.go ではなく test_*.go と命名されています。これは、Goの標準テストツールである go test*_test.go ファイルを自動的に検出して実行するのに対し、これらのファイルは go test の対象ではないことを明確にするためです。vet のテストは、Makefile を介して特定のビルドタグ付きでコンパイルされ、その後、../../../test/errchk のようなカスタムテストスクリプトによって実行されます。これは、vet がコードの静的解析を行う性質上、通常のユニットテストとは異なる検証プロセスが必要となるためです。

これらの変更により、cmd/vet のコードベースはより整理され、テストの実行方法も明確になりました。プロダクションコードとテストコードの分離は、大規模なプロジェクトにおいてコードの健全性を維持し、開発効率を高める上で非常に重要なプラクティスです。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更は、主に以下の2つのパターンに集約されます。

  1. 既存のソースファイルからのテストコードの削除: src/cmd/vet/atomic.gosrc/cmd/vet/buildtag.gosrc/cmd/vet/method.gosrc/cmd/vet/print.gosrc/cmd/vet/print_unsafe.gosrc/cmd/vet/rangeloop.gosrc/cmd/vet/structtag.gosrc/cmd/vet/taglit.go といったファイルから、テスト目的で記述されていた関数や型定義が削除されています。

    例: src/cmd/vet/atomic.go からの削除

    --- a/src/cmd/vet/atomic.go
    +++ b/src/cmd/vet/atomic.go
    @@ -7,7 +7,6 @@ package main
     import (
     	"go/ast"
     	"go/token"
    -	"sync/atomic"
     )
    
     // checkAtomicAssignment walks the assignment statement checking for common
    @@ -58,33 +57,3 @@ func (f *File) checkAtomicAddAssignment(left ast.Expr, call *ast.CallExpr) {
     		f.Warn(left.Pos(), "direct assignment to atomic value")
     	}
     }
    -
    -type Counter uint64
    -
    -func BadAtomicAssignmentUsedInTests() {
    -	x := uint64(1)
    -	x = atomic.AddUint64(&x, 1)        // ERROR "direct assignment to atomic value"
    -	_, x = 10, atomic.AddUint64(&x, 1) // ERROR "direct assignment to atomic value"
    -	x, _ = atomic.AddUint64(&x, 1), 10 // ERROR "direct assignment to atomic value"
    -
    -	y := &x
    -	*y = atomic.AddUint64(y, 1) // ERROR "direct assignment to atomic value"
    -
    -	var su struct{ Counter uint64 }
    -	su.Counter = atomic.AddUint64(&su.Counter, 1) // ERROR "direct assignment to atomic value"
    -	z1 := atomic.AddUint64(&su.Counter, 1)
    -	_ = z1 // Avoid err "z declared and not used"
    -
    -	var sp struct{ Counter *uint64 }
    -	*sp.Counter = atomic.AddUint64(sp.Counter, 1) // ERROR "direct assignment to atomic value"
    -	z2 := atomic.AddUint64(sp.Counter, 1)
    -	_ = z2 // Avoid err "z declared and not used"
    -
    -	au := []uint64{10, 20}
    -	au[0] = atomic.AddUint64(&au[0], 1) // ERROR "direct assignment to atomic value"
    -	au[1] = atomic.AddUint64(&au[0], 1)
    -
    -	ap := []*uint64{&au[0], &au[1]}
    -	*ap[0] = atomic.AddUint64(ap[0], 1) // ERROR "direct assignment to atomic value"
    -	*ap[1] = atomic.AddUint64(ap[0], 1)
    -}
    
  2. 新しいテストファイルの作成とビルドタグの追加: 削除されたテストコードは、それぞれ対応する新しいファイル(例: src/cmd/vet/test_atomic.go)に移動され、そのファイルの先頭に // +build vet_test というビルドタグが追加されています。

    例: src/cmd/vet/test_atomic.go の新規作成

    --- /dev/null
    +++ b/src/cmd/vet/test_atomic.go
    @@ -0,0 +1,43 @@
    +// Copyright 2013 The Go Authors. All rights reserved.
    +// Use of this source code is governed by a BSD-style
    +// license that can be found in the LICENSE file.
    +
    +// +build vet_test
    +
    +// This file contains tests for the atomic checker.
    +
    +package main
    +
    +import (
    +	"sync/atomic"
    +)
    +
    +type Counter uint64
    +
    +func AtomicTests() {
    +	x := uint64(1)
    +	x = atomic.AddUint64(&x, 1)        // ERROR "direct assignment to atomic value"
    +	_, x = 10, atomic.AddUint64(&x, 1) // ERROR "direct assignment to atomic value"
    +	x, _ = atomic.AddUint64(&x, 1), 10 // ERROR "direct assignment to atomic value"
    +
    +	y := &x
    +	*y = atomic.AddUint64(y, 1) // ERROR "direct assignment to atomic value"
    +
    +	var su struct{ Counter uint64 }
    +	su.Counter = atomic.AddUint64(&su.Counter, 1) // ERROR "direct assignment to atomic value"
    +	z1 := atomic.AddUint64(&su.Counter, 1)
    +	_ = z1 // Avoid err "z declared and not used"
    +
    +	var sp struct{ Counter *uint64 }
    +	*sp.Counter = atomic.AddUint64(sp.Counter, 1) // ERROR "direct assignment to atomic value"
    +	z2 := atomic.AddUint64(sp.Counter, 1)
    +	_ = z2 // Avoid err "z declared and not used"
    +
    +	au := []uint64{10, 20}
    +	au[0] = atomic.AddUint64(&au[0], 1) // ERROR "direct assignment to atomic value"
    +	au[1] = atomic.AddUint64(&au[0], 1)
    +
    +	ap := []*uint64{&au[0], &au[1]}
    +	*ap[0] = atomic.AddUint64(ap[0], 1) // ERROR "direct assignment to atomic value"
    +	*ap[1] = atomic.AddUint64(ap[0], 1)
    +}
    
  3. src/cmd/vet/Makefile の変更: test ターゲットのビルドコマンドが go build -tags unsafe から go build -tags vet_test に変更されています。

    --- a/src/cmd/vet/Makefile
    +++ b/src/cmd/vet/Makefile
    @@ -3,6 +3,6 @@
     # license that can be found in the LICENSE file.
    
     test testshort:
    -	go build -tags unsafe
    +	go build -tags vet_test
     	../../../test/errchk ./vet -compositewhitelist=false -printfuncs='Warn:1,Warnf:1' *.go
    

これらの変更により、vet のテストコードは独立したファイルに存在し、vet_test ビルドタグが指定された場合にのみコンパイルされるようになりました。

コアとなるコードの解説

このコミットのコアとなる変更は、cmd/vet のテスト戦略の再構築にあります。

テストコードの分離とビルドタグの活用: 以前は、vet の各チェック機能(例: atomicprintrangeloop など)のソースファイル内に、そのチェック機能が正しく動作するかを検証するためのテストコードが直接記述されていました。これらのテストコードは、vet が検出するべきエラーパターンを意図的に含んでおり、vet を実行した際に期待されるエラーメッセージが出力されることを確認するために使用されていました。

このコミットでは、これらのテストコードが元のソースファイルから完全に削除され、それぞれ独立した test_*.go ファイルに移動されました。例えば、atomic.go にあったアトミック操作に関するテストは test_atomic.go に、print.go にあった Printf フォーマットに関するテストは test_print.go に移動されました。

これらの新しいテストファイルには、共通して // +build vet_test というビルドタグが付与されています。このビルドタグの目的は、これらのテストファイルが通常の go build コマンドで cmd/vet のプロダクションバイナリをビルドする際にはコンパイル対象から除外されるようにすることです。これにより、vet ツールの配布バイナリにテストコードが含まれることがなくなり、バイナリサイズが最適化されます。

Makefile を介したテストの実行: vet のテストは、Goの標準的な go test コマンドでは実行されません。これは、これらのテストが vet ツール自体がエラーを検出することを期待する性質のものであり、通常のユニットテストとは異なる検証プロセスが必要となるためです。 代わりに、src/cmd/vet/Makefile が更新され、test ターゲットが go build -tags vet_test を使用するように変更されました。make test を実行すると、このコマンドが実行され、vet_test タグを持つすべてのテストファイルがコンパイルされ、vet ツールをテストするための特別なバイナリが生成されます。 その後、../../../test/errchk ./vet ... のようなコマンドが実行されます。errchk はGoのテストスイート内で使用されるユーティリティであり、指定されたコマンド(この場合はビルドされた vet バイナリ)を実行し、その出力が期待されるエラーメッセージと一致するかどうかを検証します。これにより、vet が意図した通りにコードの問題を検出できることが保証されます。

命名規則 test_*.go の意図: ファイル名に _test.go ではなく test_*.go を使用しているのは、これらのファイルが go test の自動検出メカニズムの対象外であることを明確にするためです。これは、vet のテストが通常のGoパッケージのテストとは異なるライフサイクルと実行方法を持つことを示しています。

この変更は、cmd/vet のコードベースの構造を改善し、プロダクションコードとテストコードの明確な分離を実現することで、長期的な保守性と開発効率を向上させるものです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ (GitHub)
  • Gitのコミットログと差分表示
  • Makefile の一般的な知識