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

[インデックス 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言語のテストフレームワークの基盤を構築し、より効率的でスケーラブルなテスト実行環境を提供しようとする意図があります。具体的には、以下の課題を解決しようとしています。

  1. ビルドと実行のオーバーヘッド: 各テストが独立したバイナリであるため、コンパイルとリンクのプロセスがテストごとに繰り返され、非効率でした。
  2. テスト結果の集約: 個別のテスト結果をまとめて報告する仕組みが不十分でした。
  3. テストコードの再利用性: テストコードがmainパッケージに属しているため、他のパッケージからテストヘルパー関数などを再利用することが困難でした。

この変更は、Go言語の標準ライブラリにおけるテストの構造化と自動化に向けた重要な一歩であり、後のgo testコマンドの基盤となる考え方を示しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の初期の概念と一般的なプログラミングの知識が必要です。

  • Go言語のパッケージシステム: Go言語では、コードはパッケージにまとめられます。mainパッケージは実行可能なプログラムのエントリポイントであり、それ以外のパッケージはライブラリとして機能します。このコミットでは、テストコードがmainパッケージからstrconvパッケージ(および新しく導入されるtestingパッケージ)に移行しています。
  • exportキーワード (Go言語初期): Go言語の非常に初期のバージョンでは、関数や変数を外部に公開するためにexportキーワードが使用されていました。これは後に、識別子の最初の文字を大文字にすることで公開されるという現在のGoの慣習に置き換えられました。このコミットのコードにはexport funcという記述が見られ、当時のGoの言語仕様を反映しています。
  • 浮動小数点数演算と誤差: strconvパッケージは文字列と数値の変換を扱います。特に浮動小数点数の変換(atof64ftoa64)は、精度や丸め誤差、オーバーフロー/アンダーフローといった問題が伴います。テストコードでは、これらのエッジケースが適切に処理されるかどうかが検証されています。
  • 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言語のテストの実行モデルを根本的に変更した点にあります。

  1. パッケージの変更:

    • src/lib/strconv/testatof.go, src/lib/strconv/testfp.go, src/lib/strconv/testftoa.goの各ファイルで、package mainpackage strconvに変更されています。これにより、これらのテストコードは独立した実行ファイルではなく、strconvパッケージの一部としてコンパイルされるようになります。
    • テスト関数(例: main()関数)は、export func TestAtof() boolのような形式に変更され、strconvパッケージからエクスポートされる関数となりました。これにより、外部のテストランナーからこれらの関数を呼び出すことが可能になります。
  2. 新しいtestingパッケージの導入:

    • src/lib/strconv/testing.goという新しいファイルが追加されています。このファイルはpackage testingとして定義されており、Go言語の標準testingパッケージの初期の形と考えられます。
    • このtestingパッケージには、Testという構造体(テスト名とテスト関数へのポインタを持つ)と、Mainという関数が含まれています。Main関数は、Test構造体のスライスを受け取り、各テスト関数を実行し、その結果(PASS/FAIL)を標準出力に出力します。テストが一つでも失敗した場合、プログラムは終了コード1で終了します。
  3. テスト実行スクリプトの簡素化:

    • src/lib/strconv/test.bashから、個別のテストバイナリをビルド・実行するコマンド(6g testatof.go, 6l testatof.6, 6.outなど)が削除されています。これは、テストが単一のバイナリに統合されたことを反映しています。
  4. エラーハンドリングの改善:

    • 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言語の公式ドキュメント
  • Go言語のソースコードリポジトリ (特に初期のコミット履歴)
  • Go言語の設計に関する議論やブログ記事
  • Go言語のtestingパッケージの進化に関する資料
  • 浮動小数点数演算に関する一般的な知識
  • Go言語のエラーハンドリングの歴史に関する情報