[インデックス 1161] ファイルの概要
このコミットは、Go言語の標準ライブラリであるstrconv
パッケージのテスト構造を大幅に改善し、複数のテストバイナリを単一のテスト実行ファイルに統合するものです。これにより、テストの実行効率と管理性が向上しています。
コミット
commit c1efd7d6e5a97cce233ecb6bb59d19b55eb33c3c
Author: Russ Cox <rsc@golang.org>
Date: Tue Nov 18 16:13:25 2008 -0800
roll tests into one binary
R=r
DELTA=145 (27 added, 27 deleted, 91 changed)
OCL=19423
CL=19502
---
src/lib/strconv/test.bash | 13 +----
src/lib/strconv/testatof.go | 137 +++++++++++++++++++++-----------------------
src/lib/strconv/testfp.go | 42 ++++++--------
src/lib/strconv/testftoa.go | 22 ++++---
src/lib/strconv/testing.go | 26 +++++++++
5 files changed, 122 insertions(+), 118 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c1efd7d6e5a97cce233ecb6bb59d19b55eb33c3c
元コミット内容
このコミットの元のメッセージは「roll tests into one binary」です。これは、複数のテストを個別のバイナリとして実行するのではなく、単一の実行可能なテストスイートにまとめることを意図しています。
変更の背景
Go言語の初期段階では、各テストファイルが独立したmain
パッケージとしてコンパイルされ、個別の実行ファイルとして実行されていました。これは、テストの数が増えるにつれて、ビルド時間と実行時間の増大、およびテスト管理の複雑化を招きます。
このコミットの背景には、Go言語のテストフレームワークの基盤を構築し、より効率的でスケーラブルなテスト実行環境を提供しようとする意図があります。具体的には、以下の課題を解決しようとしています。
- ビルドと実行のオーバーヘッド: 各テストが独立したバイナリであるため、コンパイルとリンクのプロセスがテストごとに繰り返され、非効率でした。
- テスト結果の集約: 個別のテスト結果をまとめて報告する仕組みが不十分でした。
- テストコードの再利用性: テストコードが
main
パッケージに属しているため、他のパッケージからテストヘルパー関数などを再利用することが困難でした。
この変更は、Go言語の標準ライブラリにおけるテストの構造化と自動化に向けた重要な一歩であり、後のgo test
コマンドの基盤となる考え方を示しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の初期の概念と一般的なプログラミングの知識が必要です。
- Go言語のパッケージシステム: Go言語では、コードはパッケージにまとめられます。
main
パッケージは実行可能なプログラムのエントリポイントであり、それ以外のパッケージはライブラリとして機能します。このコミットでは、テストコードがmain
パッケージからstrconv
パッケージ(および新しく導入されるtesting
パッケージ)に移行しています。 export
キーワード (Go言語初期): Go言語の非常に初期のバージョンでは、関数や変数を外部に公開するためにexport
キーワードが使用されていました。これは後に、識別子の最初の文字を大文字にすることで公開されるという現在のGoの慣習に置き換えられました。このコミットのコードにはexport func
という記述が見られ、当時のGoの言語仕様を反映しています。- 浮動小数点数演算と誤差:
strconv
パッケージは文字列と数値の変換を扱います。特に浮動小数点数の変換(atof64
やftoa64
)は、精度や丸め誤差、オーバーフロー/アンダーフローといった問題が伴います。テストコードでは、これらのエッジケースが適切に処理されるかどうかが検証されています。 os.Error
(Go言語初期): Go言語の初期のバージョンでは、エラーはos.Error
型として表現されていました。これは後に、より汎用的なerror
インターフェースに置き換えられます。コミット内のerr *os.Error
という記述は、当時のエラーハンドリングの慣習を示しています。- シェルスクリプト (
.bash
ファイル):test.bash
ファイルは、テストのビルドと実行を自動化するためのシェルスクリプトです。このコミットでは、Goのテストフレームワークが進化するにつれて、このシェルスクリプトの役割が縮小または変更される様子が示されています。 6g
,6l
,6.out
: これらはGo言語の初期のコンパイラ (6g
for Go,6l
for linker) と、生成される実行ファイル (6.out
) の命名規則です。当時のGoのツールチェインの様子を垣間見ることができます。
技術的詳細
このコミットの技術的な核心は、Go言語のテストの実行モデルを根本的に変更した点にあります。
-
パッケージの変更:
src/lib/strconv/testatof.go
,src/lib/strconv/testfp.go
,src/lib/strconv/testftoa.go
の各ファイルで、package main
がpackage strconv
に変更されています。これにより、これらのテストコードは独立した実行ファイルではなく、strconv
パッケージの一部としてコンパイルされるようになります。- テスト関数(例:
main()
関数)は、export func TestAtof() bool
のような形式に変更され、strconv
パッケージからエクスポートされる関数となりました。これにより、外部のテストランナーからこれらの関数を呼び出すことが可能になります。
-
新しい
testing
パッケージの導入:src/lib/strconv/testing.go
という新しいファイルが追加されています。このファイルはpackage testing
として定義されており、Go言語の標準testing
パッケージの初期の形と考えられます。- この
testing
パッケージには、Test
という構造体(テスト名とテスト関数へのポインタを持つ)と、Main
という関数が含まれています。Main
関数は、Test
構造体のスライスを受け取り、各テスト関数を実行し、その結果(PASS/FAIL)を標準出力に出力します。テストが一つでも失敗した場合、プログラムは終了コード1で終了します。
-
テスト実行スクリプトの簡素化:
src/lib/strconv/test.bash
から、個別のテストバイナリをビルド・実行するコマンド(6g testatof.go
,6l testatof.6
,6.out
など)が削除されています。これは、テストが単一のバイナリに統合されたことを反映しています。
-
エラーハンドリングの改善:
testatof.go
では、strconv.atof64
の戻り値がf, overflow, ok
からout, err
に変更されています。これは、Go言語がエラーをerror
インターフェースで返すという現在の慣習に近づく初期のステップです。テストケースの定義もTest{ "1", "1" }
からTest{ "1", "1", nil }
のように、期待されるエラー情報を含むように変更されています。これにより、テストがより詳細なエラー条件を検証できるようになります。
これらの変更により、Goのテストはよりモジュール化され、自動化されたテストフレームワークの基盤が築かれました。
コアとなるコードの変更箇所
src/lib/strconv/testatof.go
--- a/src/lib/strconv/testatof.go
+++ b/src/lib/strconv/testatof.go
@@ -2,109 +2,100 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package main
-
-import "strconv"
+package strconv
+import (
+\t"fmt";
+\t"os";
+\t"strconv"
+)
type Test struct {
\tin string;
\tout string;
+\terr *os.Error;
}
var tests = []Test {
-\tTest{ "1", "1" },
-\tTest{ "1e23", "1e+23" },
-\tTest{ "100000000000000000000000", "1e+23" },
-\tTest{ "1e-100", "1e-100" },
-\tTest{ "123456700", "1.234567e+08" },
-\tTest{ "99999999999999974834176", "9.999999999999997e+22" },
-\tTest{ "100000000000000000000001", "1.0000000000000001e+23" },
-\tTest{ "100000000000000008388608", "1.0000000000000001e+23" },
-\tTest{ "100000000000000016777215", "1.0000000000000001e+23" },
-\tTest{ "100000000000000016777216", "1.0000000000000003e+23" },
-\tTest{ "1", "1", nil },
+\tTest{ "1", "1", nil },
+\tTest{ "1e23", "1e+23", nil },
+\tTest{ "100000000000000000000000", "1e+23", nil },
+\tTest{ "1e-100", "1e-100", nil },
+\tTest{ "123456700", "1.234567e+08", nil },
+\tTest{ "99999999999999974834176", "9.999999999999997e+22", nil },
+\tTest{ "100000000000000000000001", "1.0000000000000001e+23", nil },
+\tTest{ "100000000000000008388608", "1.0000000000000001e+23", nil },
+\tTest{ "100000000000000016777215", "1.0000000000000001e+23", nil },
+\tTest{ "100000000000000016777216", "1.0000000000000003e+23", nil },
+\tTest{ "-1", "-1", nil },
+\tTest{ "-0", "0", nil },
+\tTest{ "1e-20", "1e-20", nil },
\t// largest float64
-\tTest{ "1.7976931348623157e308", "1.7976931348623157e+308" },
-\tTest{ "-1.7976931348623157e308", "1.7976931348623157e+308" },
+\tTest{ "1.7976931348623157e308", "1.7976931348623157e+308", nil },
+\tTest{ "-1.7976931348623157e308", "-1.7976931348623157e+308", nil },
\t// next float64 - too large
-\tTest{ "1.7976931348623159e308", "+Inf" },
-\tTest{ "-1.7976931348623159e308", "-Inf" },
+\tTest{ "1.7976931348623159e308", "+Inf", os.ERANGE },
+\tTest{ "-1.7976931348623159e308", "-Inf", os.ERANGE },
\t// the border is ...158079
\t// borderline - okay
-\tTest{ "1.7976931348623158e308", "1.7976931348623157e+308" },
-\tTest{ "-1.7976931348623158e308", "1.7976931348623157e+308" },
+\tTest{ "1.7976931348623158e308", "1.7976931348623157e+308", nil },
+\tTest{ "-1.7976931348623158e308", "-1.7976931348623157e+308", nil },
\t// borderline - too large
-\tTest{ "1.797693134862315808e308", "+Inf" },
-\tTest{ "-1.797693134862315808e308", "-Inf" },
+\tTest{ "1.797693134862315808e308", "+Inf", os.ERANGE },
+\tTest{ "-1.797693134862315808e308", "-Inf", os.ERANGE },
\t// a little too large
-\tTest{ "1e308", "1e+308" },
-\tTest{ "2e308", "+Inf" },
-\tTest{ "1e309", "+Inf" },
+\tTest{ "1e308", "1e+308", nil },
+\tTest{ "2e308", "+Inf", os.ERANGE },
+\tTest{ "1e309", "+Inf", os.ERANGE },
\t// way too large
-\tTest{ "1e310", "+Inf" },
-\tTest{ "1e310", "-Inf" },
-\tTest{ "1e400", "+Inf" },
-\tTest{ "1e400", "-Inf" },
-\tTest{ "1e400000", "+Inf" },
-\tTest{ "1e400000", "-Inf" },
+\tTest{ "1e310", "+Inf", os.ERANGE },
+\tTest{ "-1e310", "-Inf", os.ERANGE },
+\tTest{ "1e400", "+Inf", os.ERANGE },
+\tTest{ "-1e400", "-Inf", os.ERANGE },
+\tTest{ "1e400000", "+Inf", os.ERANGE },
+\tTest{ "-1e400000", "-Inf", os.ERANGE },
\t// denormalized
-\tTest{ "1e-305", "1e-305" },
-\tTest{ "1e-306", "1e-306" },
-\tTest{ "1e-307", "1e-307" },
-\tTest{ "1e-308", "1e-308" },
-\tTest{ "1e-309", "1e-309" },
-\tTest{ "1e-310", "1e-310" },
-\tTest{ "1e-322", "1e-322" },
+\tTest{ "1e-305", "1e-305", nil },
+\tTest{ "1e-306", "1e-306", nil },
+\tTest{ "1e-307", "1e-307", nil },
+\tTest{ "1e-308", "1e-308", nil },
+\tTest{ "1e-309", "1e-309", nil },
+\tTest{ "1e-310", "1e-310", nil },
+\tTest{ "1e-322", "1e-322", nil },
\t// smallest denormal
-\tTest{ "5e-324", "5e-324" },
+\tTest{ "5e-324", "5e-324", nil },
\t// too small
-\tTest{ "4e-324", "0" },
+\tTest{ "4e-324", "0", nil },
\t// way too small
-\tTest{ "1e-350", "0" },
-\tTest{ "1e-400000", "0" },
+\tTest{ "1e-350", "0", nil },
+\tTest{ "1e-400000", "0", nil },
\t// try to overflow exponent
-\tTest{ "1e-4294967296", "0" },
-\tTest{ "1e+4294967296", "+Inf" },
-\tTest{ "1e-18446744073709551616", "0" },
-\tTest{ "1e+18446744073709551616", "+Inf" },
+\tTest{ "1e-4294967296", "0", nil },
+\tTest{ "1e+4294967296", "+Inf", os.ERANGE },
+\tTest{ "1e-18446744073709551616", "0", nil },
+\tTest{ "1e+18446744073709551616", "+Inf", os.ERANGE },
\t// Parse errors
-\tTest{ "1e", "error" },
-\tTest{ "1e-", "error" },
-\tTest{ ".e-1", "error" },
+\tTest{ "1e", "0", os.EINVAL },
+\tTest{ "1e-", "0", os.EINVAL },
+\tTest{ ".e-1", "0", os.EINVAL },
}\n \n-func main() {\n-\tbad := 0;\n+export func TestAtof() bool {\n+\tok := true;\n \tfor i := 0; i < len(tests); i++ {\n \t\tt := &tests[i];\n-\t\tf, overflow, ok := strconv.atof64(t.in);\n-\t\tif !ok && t.out == "error" {\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif !ok {\n-\t\t\tpanicln("test:", t.in, "failed to parse");\n-\t\t}\n-\t\tif overflow && !sys.isInf(f, 0) {\n-\t\t\tpanicln("overflow but not inf:", t.in, f);\n+\t\tout, err := strconv.atof64(t.in);\n+\t\touts := strconv.ftoa64(out, 'g', -1);\n+\t\tif outs != t.out || err != t.err {\n+\t\t\tfmt.printf("strconv.atof64(%v) = %v, %v want %v, %v\\n",\n+\t\t\t\tt.in, out, err, t.out, t.err);\n+\t\t\tok = false;\n \t\t}\n-\t\tif sys.isInf(f, 0) && !overflow {\n-\t\t\tpanicln("inf but not overflow:", t.in, f);\n-\t\t}\n-\t\ts := strconv.ftoa64(f, 'g', -1);\n-\t\tif s != t.out {\n-\t\t\tprintln("test", t.in, "want", t.out, "got", s);\n-\t\t\tbad++;\n-\t\t}\n-\t}\n-\tif bad != 0 {\n-\t\tpanic("failed");\n \t}\n+\treturn ok;\n }\n```
### `src/lib/strconv/testing.go` (新規ファイル)
```go
// Copyright 2009 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.
package testing
export type Test struct {
name string;
f *() bool;
}
export func Main(tests *[]Test) {
ok := true;
for i := 0; i < len(tests); i++ {
ok1 := tests[i].f();
status := "FAIL";
if ok1 {
status = "PASS"
}
ok = ok && ok1;
println(status, tests[i].name);
}
if !ok {
sys.exit(1);
}
}
コアとなるコードの解説
src/lib/strconv/testatof.go
の変更点
- パッケージの変更:
package main
からpackage strconv
へ変更されました。これにより、このファイルは独立した実行ファイルではなく、strconv
パッケージの一部としてコンパイルされます。 Test
構造体の拡張:Test
構造体にerr *os.Error
フィールドが追加されました。これにより、テストケースで期待されるエラーの種類を指定できるようになり、より厳密なエラー検証が可能になります。- テスト関数の変更:
func main()
がexport func TestAtof() bool
に変更されました。export
キーワードは、この関数がパッケージ外から呼び出し可能であることを示します(Goの初期の仕様)。- 関数が
bool
を返すようになり、テストの成功/失敗を明示的に示すようになりました。 - テストの失敗時に
panicln
を呼び出す代わりに、fmt.printf
で詳細なエラーメッセージを出力し、ok = false
を設定するようになりました。これにより、テストスイート全体が中断されることなく、すべてのテストが実行され、最終的な結果が報告されるようになります。
strconv.atof64
の呼び出しとエラーハンドリングの変更:- 以前の
f, overflow, ok := strconv.atof64(t.in)
から、out, err := strconv.atof64(t.in)
に変更されました。これは、Go言語がエラーをerror
インターフェースで返すという現在の慣習に近づく初期のステップです。 - エラーチェックも
!ok
からerr != t.err
に変更され、期待されるエラーと実際のエラーを比較するようになりました。
- 以前の
src/lib/strconv/testing.go
の新規追加
このファイルは、Go言語のテストフレームワークの初期の形を定義しています。
package testing
: 新しいtesting
パッケージを定義します。export type Test struct
:name string
: テストの名前を保持します。f *() bool
: テスト関数へのポインタを保持します。この関数は引数を取らず、bool
(成功/失敗)を返します。
export func Main(tests *[]Test)
:- この関数は、
Test
構造体のスライスを受け取り、各テストを実行するテストランナーとして機能します。 - ループ内で各テスト関数
tests[i].f()
を呼び出し、その戻り値に基づいてPASS
またはFAIL
を標準出力に出力します。 - いずれかのテストが失敗した場合、
ok
変数をfalse
に設定し、最終的にsys.exit(1)
を呼び出してプログラムをエラー終了させます。これにより、CI/CDシステムなどでテスト結果を自動的に判断できるようになります。
- この関数は、
これらの変更により、Goのテストは個別のスクリプト実行から、Go言語のコード内で定義・実行される統一されたフレームワークへと進化しました。
関連リンク
- Go言語の
strconv
パッケージ: https://pkg.go.dev/strconv - Go言語の
testing
パッケージ: https://pkg.go.dev/testing - Go言語の初期の歴史に関する情報源 (例: Go Wiki, Go Blogの初期の記事など)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコードリポジトリ (特に初期のコミット履歴)
- Go言語の設計に関する議論やブログ記事
- Go言語の
testing
パッケージの進化に関する資料 - 浮動小数点数演算に関する一般的な知識
- Go言語のエラーハンドリングの歴史に関する情報