[インデックス 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
など)内に直接記述されていました。これは、開発初期段階では便利かもしれませんが、いくつかの問題を引き起こします。
- バイナリサイズの増大: テストコードは通常、最終的なプロダクションバイナリには含まれるべきではありません。ソースコード内にテストが混在していると、
vet
ツールのバイナリサイズが不必要に大きくなります。 - コードの分離の欠如: プロダクションコードとテストコードが同じファイルに混在していると、コードの可読性や保守性が低下します。コードベースが成長するにつれて、この問題はより顕著になります。
- ビルドプロセスの複雑化: テストコードをビルドから除外するために、特別なビルド設定や条件付きコンパイルが必要になる場合があります。
このコミットの背景には、これらの問題を解決し、cmd/vet
の開発とメンテナンスをより効率的にするという目的があります。テストコードを独立したファイルに移動し、ビルドタグを使用することで、これらの問題に対処しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とツールの知識が必要です。
-
cmd/vet
: Go言語の標準ツールチェーンに含まれる静的解析ツールです。go vet
コマンドとして実行され、Goソースコードを分析して、一般的なプログラミングエラー(例:Printf
フォーマット文字列と引数の不一致、構造体タグの誤り、アトミック操作の誤用、range
ループ変数のクロージャでの誤用など)を検出します。これらのチェックは、コンパイル時には検出されないが、実行時に問題を引き起こす可能性のあるコードパターンを特定するのに役立ちます。 -
Goのビルドタグ (Build Tags): Goのビルドタグは、特定の条件に基づいてソースファイルを含めたり除外したりするためのメカニズムです。Goソースファイルの先頭に
// +build tagname
の形式でコメントとして記述されます。ビルドコマンド(go build
,go run
,go test
など)に-tags
フラグを指定することで、特定のタグを持つファイルのみをコンパイル対象に含めることができます。 例えば、// +build debug
と書かれたファイルは、go build -tags debug
と実行された場合にのみコンパイルされます。複数のタグをスペースで区切って指定したり、!
を使ってタグを除外したりすることも可能です(例:// +build linux,amd64 !debug
)。 -
go test
とテストファイルの命名規則: Go言語には、組み込みのテストフレームワークがあり、go test
コマンドで実行されます。go test
は、ファイル名が_test.go
で終わるファイルを自動的にテストファイルとして認識し、その中のTestXxx
、BenchmarkXxx
、ExampleXxx
という関数を実行します。 このコミットでは、test_*.go
という命名規則が採用されています。これは、これらのファイルがgo test
コマンドによって直接実行される通常のユニットテストではないことを示しています。代わりに、vet
ツール自体の動作を検証するための特別なテストであり、Makefile
を介して実行されることを意図しています。 -
Makefile
:Makefile
は、ソフトウェアのビルドプロセスを自動化するためのツールであるmake
コマンドによって使用される設定ファイルです。このファイルには、ソースコードのコンパイル、テストの実行、パッケージの作成など、プロジェクトのビルドに関連するタスクを定義するルールが記述されます。このコミットでは、Makefile
が更新され、vet
ツールのテストを実行するための新しいルールが追加されています。
技術的詳細
このコミットの技術的な核心は、cmd/vet
のテストコードを、Goのビルドシステムと Makefile
を利用して、プロダクションコードから分離し、効率的に管理することにあります。
-
テストコードの分離:
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
ツールの主要なロジックを含むファイルは、テストコードを含まないクリーンな状態になりました。 -
ビルドタグ
vet_test
の導入: 新しく作成されたすべてのテストファイル(test_*.go
)の先頭には、// +build vet_test
というビルドタグが追加されました。このタグは、これらのファイルが通常のgo build
コマンドではコンパイルされないことを意味します。 以前はprint_unsafe.go
のように// +build unsafe
タグを使用していたファイルもありましたが、このコミットではその内容もtest_print.go
に移動され、vet_test
タグの下に統合されました。これにより、vet
のテストに関連するすべてのファイルが単一のビルドタグで管理されるようになります。 -
Makefile
の更新:src/cmd/vet/Makefile
が変更され、test
ターゲットのビルドコマンドが更新されました。 変更前:go build -tags unsafe
変更後:go build -tags vet_test
この変更により、make test
コマンドが実行されると、go build
はvet_test
タグを持つファイル(つまり、新しく分離されたテストファイル群)をコンパイル対象に含めるようになります。これにより、vet
ツール自体をテストするための特別なバイナリが生成され、そのバイナリに対してテストが実行される仕組みが確立されました。 -
テストファイルの命名規則
test_*.go
: コミットメッセージで明示されているように、これらのテストファイルは*_test.go
ではなくtest_*.go
と命名されています。これは、Goの標準テストツールであるgo test
が*_test.go
ファイルを自動的に検出して実行するのに対し、これらのファイルはgo test
の対象ではないことを明確にするためです。vet
のテストは、Makefile
を介して特定のビルドタグ付きでコンパイルされ、その後、../../../test/errchk
のようなカスタムテストスクリプトによって実行されます。これは、vet
がコードの静的解析を行う性質上、通常のユニットテストとは異なる検証プロセスが必要となるためです。
これらの変更により、cmd/vet
のコードベースはより整理され、テストの実行方法も明確になりました。プロダクションコードとテストコードの分離は、大規模なプロジェクトにおいてコードの健全性を維持し、開発効率を高める上で非常に重要なプラクティスです。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下の2つのパターンに集約されます。
-
既存のソースファイルからのテストコードの削除:
src/cmd/vet/atomic.go
、src/cmd/vet/buildtag.go
、src/cmd/vet/method.go
、src/cmd/vet/print.go
、src/cmd/vet/print_unsafe.go
、src/cmd/vet/rangeloop.go
、src/cmd/vet/structtag.go
、src/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) -}
-
新しいテストファイルの作成とビルドタグの追加: 削除されたテストコードは、それぞれ対応する新しいファイル(例:
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) +}
-
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
の各チェック機能(例: atomic
、print
、rangeloop
など)のソースファイル内に、そのチェック機能が正しく動作するかを検証するためのテストコードが直接記述されていました。これらのテストコードは、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言語の
cmd/vet
ドキュメント: https://pkg.go.dev/cmd/vet - Go言語のビルドタグに関する公式ドキュメント: https://go.dev/cmd/go/#hdr-Build_constraints
- Go言語のテストに関する公式ドキュメント: https://go.dev/pkg/testing/
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコードリポジトリ (GitHub)
- Gitのコミットログと差分表示
Makefile
の一般的な知識