著者は、 Write for DOnations プログラムの一環として、 FreeBSDFoundationを選択して寄付を受け取りました。

序章

単体テストは、プログラムまたはパッケージから特定のコードをテストする関数です。 単体テストの仕事はアプリケーションの正しさをチェックすることであり、それらはGoプログラミング言語の重要な部分です。

このチュートリアルでは、小さなプログラムを作成し、Goのテストパッケージとgo testコマンドを使用して、コードで一連のテストを実行します。 チュートリアルを完了すると、テーブルベースの単体テストカバレッジテストベンチマーク、および文書化された例

前提条件

このチュートリアルを完了するには、次のものが必要です。

  • Goプログラミング言語に精通していること。 言語の幅広い紹介については、チュートリアルシリーズ/電子ブックGoでコーディングする方法をご覧ください。

  • Goバージョン1.11以降がローカルマシンにインストールされています。 これらの手順に従って、Goを Linux macOS 、およびWindowsにインストールできます。 macOSでは、Homebrewパッケージマネージャーを使用してGoをインストールすることもできます。

注:このチュートリアルでは、Goバージョン1.11で導入されたパッケージ管理システムであるGoモジュールを使用します。 Goモジュールは、$ GOPATHを置き換えることを目的としており、Goバージョン1.13以降のデフォルトオプションになりました。 Goモジュールと$GOPATHの違いのより包括的な概要については、Goコアチームからのこの公式ブログ投稿を読むことを検討してください。

このチュートリアルは、Goバージョン1.14を使用してテストされました

ステップ1—単体テスト用のサンプルプログラムを作成する

単体テストを作成する前に、テストで分析するためのコードが必要です。 このステップでは、2つの整数を合計する小さなプログラムを作成します。 以降の手順では、go testを使用してプログラムをテストします。

まず、mathという名前の新しいディレクトリを作成します。

  1. mkdir ./math

新しいディレクトリ内に移動します。

  1. cd ./math

これがプログラムのルートディレクトリになり、残りのすべてのコマンドをここから実行します。

次に、nanoまたはお好みのテキストエディタを使用して、math.goという名前の新しいファイルを作成します。

  1. nano math.go

次のコードを追加します。

./math/math.go
package math

// Add is our function that sums two integers
func Add(x, y int) (res int) {
	return x + y
}

// Subtract subtracts two integers
func Subtract(x, y int) (res int) {
	return x - y
}

ここでは、AddSubtractという2つの関数を作成しています。 各関数は2つの整数を受け入れ、それらの合計(func Add)またはそれらの差(func Subtract)のいずれかを返します。

ファイルを保存して閉じます。

このステップでは、Goでコードを記述しました。 次に、次の手順で、コードが正しく機能することを確認するための単体テストを作成します。

ステップ2—Goでユニットテストを書く

このステップでは、Goで最初のテストを作成します。 Goでテストを作成するには、テストファイルのリンクが必要です。このテストファイルは、常に_test.goで終わる必要があります。 慣例により、Goテストファイルは常に、テストしているコードが存在する同じフォルダーまたはパッケージに配置されます。 これらのファイルは、go buildコマンドを実行したときにコンパイラーによってビルドされないため、デプロイメントで終了することを心配する必要はありません。

そして、Goのすべてと同様に、言語はテストについて意見が分かれています。 Go言語は、開発者がgo testコマンドと一緒に使用するtestingと呼ばれる最小限でありながら完全なパッケージを提供します。 testingパッケージは、カバレッジテストやベンチマークなど、いくつかの便利な規則を提供します。

エディタを使用して、math_test.goという名前の新しいファイルを作成して開きます。

  1. nano math_test.go

Goのテスト関数には、func TestXxxx(t *testing.T)というシグネチャが含まれています。 つまり、すべてのテスト関数はTestという単語で始まり、その後に最初の単語が大文字になっている接尾辞が続く必要があります。 Goのテスト関数は1つのパラメーターのみを受け取ります。この場合、それはタイプtesting.Tのポインターです。 このタイプには、t.Errorf()メソッドのように、結果を出力し、エラーを画面に記録し、障害を通知するために必要な便利なメソッドが含まれています。

次のコードをmath_test.goに追加します。

./math/math_test.go

package math

import "testing"

func TestAdd(t *testing.T){

    got := Add(4, 6)
    want := 10

    if got != want {
        t.Errorf("got %q, wanted %q", got, want)
    }
}

まず、テストするパッケージの名前mathを宣言します。 次に、testingパッケージ自体をインポートします。これにより、testing.Tタイプと、パッケージによってエクスポートされた他のタイプおよびメソッドが使用可能になります。 コードとテストロジックは、TestAdd関数に含まれています。

要約すると、Goでのテストの特徴は次のとおりです。

  • 最初で唯一のパラメータはt *testing.Tでなければなりません
  • テスト関数は、単語Testで始まり、大文字で始まる単語または句が続きます(慣例では、テスト対象のメソッドの名前を使用します(例:TestAdd))。
  • テストでは、t.Errorまたはt.Failを呼び出して、失敗を示します(t.Failよりも詳細が返されるため、t.Errorを呼び出しています)。
  • t.Logを使用して、失敗しないデバッグ情報を提供できます
  • テストは、math_test.goなどのfoo_test.goという命名規則を使用してファイルに保存されます。

ファイルを保存して閉じます。

このステップでは、Goで最初のテストを作成しました。 次のステップでは、go testを使用してコードをテストします。

ステップ3—go testコマンドを使用してGoコードをテストする

このステップでは、コードをテストします。 go testは、テストの自動化に役立つ強力なサブコマンドです。 go testは、実行するテスト、テストが返す冗長性などを構成できるさまざまなフラグを受け入れます。

プロジェクトのルートディレクトリから、最初のテストを実行します。

  1. go test

次の出力が表示されます。

Output
PASS ok ./math 0.988s

PASSは、コードが期待どおりに機能していることを意味します。 テストが失敗すると、FAILが表示されます。

go testサブコマンドは、_test.goサフィックスが付いたファイルのみを検索します。 次に、go testは、これらのファイルをスキャンして、func TestXxxや、後の手順で説明するその他のいくつかの特殊機能を探します。 go testは、これらの関数を適切な方法で呼び出し、ビルドして実行し、結果を報告し、最後にすべてをクリーンアップする一時的なメインパッケージを生成します。

私たちのgo testはおそらく私たちの小さなプログラムには十分ですが、実行中のテストとそれぞれにかかる時間を確認したい場合があります。 -vフラグを追加すると、冗長性が高まります。 新しいフラグを使用してテストを再実行します。

  1. go test -v

次の出力が表示されます。

Output
=== RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok ./math 1.410s

このステップでは、go testサブコマンドを使用して基本的な単体テストを実行しました。 次のステップでは、より複雑なテーブル駆動の単体テストを作成します。

ステップ4—Goでテーブル駆動型テストを作成する

テーブル駆動テストは、さまざまな値と結果のテーブルを維持することを除けば、基本的な単体テストに似ています。 テストスイートはこれらの値を繰り返し処理し、テストコードに送信します。 このアプローチを使用して、入力とそれぞれの出力のいくつかの組み合わせをテストすることができます。

ここで、単体テストを構造体のテーブルに置き換えます。このテーブルのフィールドには、Add関数に必要な2つの引数(2つの整数)と期待される結果(それらの合計)が含まれます。

math_test.goを再度開きます:

  1. nano math_test.go

ファイル内のすべてのコードを削除し、代わりに次のテーブル駆動型単体テストを追加します。

./math/math_test.go

package math

import "testing"

// arg1 means argument 1 and arg2 means argument 2, and the expected stands for the 'result we expect'
type addTest struct {
    arg1, arg2, expected int
}

var addTests = []addTest{
    addTest{2, 3, 5},
    addTest{4, 8, 12},
    addTest{6, 9, 15},
    addTest{3, 10, 13},
    
}


func TestAdd(t *testing.T){

    for _, test := range addTests{
        if output := Add(test.arg1, test.arg2); output != test.expected {
            t.Errorf("Output %q not equal to expected %q", output, test.expected)
        }
    }
}

ここでは、構造体を定義し、Add関数の引数と期待される結果を含む構造体のテーブルにデータを入力してから、新しいTestAdd関数を記述しています。 この新しい関数では、テーブルを反復処理し、引数を実行し、出力を期待される各結果と比較して、エラーが発生した場合はそれを返します。

ファイルを保存して閉じます。

次に、-vフラグを使用してテストを実行します。

  1. go test -v

以前と同じ出力が表示されます。

Output
=== RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok ./math 1.712s

ループが繰り返されるたびに、コードはAdd関数によって計算された値を期待値と照合してテストします。

このステップでは、テーブル駆動テストを作成しました。 次のステップでは、代わりにカバレッジテストを作成します。

ステップ5—Goでカバレッジテストを作成する

このステップでは、Goカバレッジテストを記述します。 テストを作成するときは、テストがカバーする実際のコードの量を知ることが重要になることがよくあります。 これは一般にカバレッジと呼ばれます。 これが、Subtract関数のテストを作成していない理由でもあります。そのため、不完全なカバレッジテストを表示できます。

次のコマンドを実行して、現在の単体テストのカバレッジを計算します。

  1. go test -coverprofile=coverage.out

次の出力が表示されます。

Output
PASS coverage: 50.0% of statements ok ./math 2.073s

このカバレッジデータをファイルcoverage.outに保存します。 これで、結果をWebブラウザに表示できます。

次のコマンドを実行します。

  1. go tool cover -html=coverage.out

Webブラウザーが開き、結果が次のように表示されます。

go unit testing coverage.out

緑のテキストはカバレッジを示し、赤のテキストはその反対を示します。

このステップでは、テーブル駆動の単体テストのカバレッジをテストしました。 次のステップでは、関数のベンチマークを行います。

ステップ6—Goでベンチマークを書く

このステップでは、Goベンチマークテストを作成します。 ベンチマークは、機能またはプログラムのパフォーマンスを測定します。 これにより、実装を比較し、コードに加えた変更の影響を理解できます。 その情報を使用して、最適化する価値のあるGoソースコードの一部を明らかにすることができます。

Goでは、func BenchmarkXxx(*testing.B)の形式の関数がベンチマークと見なされます。 go testは、-benchフラグを指定すると、これらのベンチマークを実行します。 ベンチマークは順番に実行されます。

単体テストにベンチマークを追加しましょう。

math_test.goを開きます:

  1. nano math_test.go

次に、func BenchmarkXxx(*testing.B)構文を使用してbenchamrk関数を追加します。

./math_test.go
...
func BenchmarkAdd(b *testing.B){
    for i :=0; i < b.N ; i++{
        Add(4, 6)
    }
}

ベンチマーク関数は、ターゲットコードをbN回実行する必要があります。ここで、Nは調整可能な整数です。 ベンチマークの実行中、bNは、ベンチマーク機能が確実にタイミングをとれるのに十分長く続くまで調整されます。 --benchフラグは、正規表現の形式で引数を受け入れます。

ファイルを保存して閉じます。

次に、go testをもう一度使用してベンチマークを実行してみましょう。

  1. go test -bench=.

.は、ファイル内のすべてのベンチマーク関数と一致します。

ベンチマーク関数を明示的に宣言することもできます。

  1. go test -bench=Add

いずれかのコマンドを実行すると、次のような出力が表示されます。

Output
goos: windows goarch: amd64 pkg: math BenchmarkAdd-4 1000000000 1.07 ns/op PASS ok ./math 2.074s

結果の出力は、ループがループあたり1.07ナノ秒の速度で10,000,000回実行されたことを意味します。

注:他の目的で使用されているビジー状態のシステムでGoコードのベンチマークを行わないようにしてください。ベンチマークプロセスに干渉し、不正確な結果が得られます。

これで、成長する単体テストにベンチマークが追加されました。 次の最後のステップでは、ドキュメントに例を追加します。これはgo testでも評価されます。

ステップ7—例を使用してGoコードを文書化する

このステップでは、Goコードを例とともに文書化し、それらの例をテストします。 Goは適切なドキュメントに非常に重点を置いており、サンプルコードはドキュメントとテストの両方に別の側面を追加します。 例は、既存のメソッドと関数に基づいています。 例では、特定のコードの使用方法をユーザーに示す必要があります。 関数の例は、go testサブコマンドによって特別に処理される3番目のタイプの関数です。

開始するには、math_test.goを再度開き、

  1. nano math_test.go

次に、強調表示されたコードを追加します。 これにより、 fmtパッケージがインポートリストに追加され、ファイルの最後にサンプル関数が追加されます。

./math/math_test.go

package math

import (
    "fmt"
    "testing"
)

// arg1 means argument 1 and arg2 means argument 2, and the expected stands for the 'result we expect'
type addTest struct {
    arg1, arg2, expected int
}

var addTests = []addTest{
    addTest{2, 3, 5},
    addTest{4, 8, 12},
    addTest{6, 9, 15},
    addTest{3, 10, 13},
}

func TestAdd(t *testing.T) {

    for _, test := range addTests {
        if output := Add(test.arg1, test.arg2); output != test.expected {
            t.Errorf("Output %q not equal to expected %q", output, test.expected)
        }
    }
}

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(4, 6)
    }
}


func ExampleAdd() {
    fmt.Println(Add(4, 6))
    // Output: 10
}

Output:行は、期待される出力を指定して文書化するために使用されます。

:比較では、先頭と末尾のスペースは無視されます。

ファイルを保存して閉じます。

次に、単体テストを再実行します。

  1. go test -v

次のような更新された出力が表示されます。

Output
=== RUN TestAdd --- PASS: TestAdd (0.00s) === RUN ExampleAdd --- PASS: ExampleAdd (0.00s) PASS ok ./math 0.442s

これで、例もテストされました。 この機能により、ドキュメントが改善され、単体テストがより堅牢になります。

結論

このチュートリアルでは、小さなプログラムを作成してから、その機能を確認するための基本的な単体テストを作成しました。 次に、単体テストをテーブルベースの単体テストとして書き直し、カバレッジテスト、ベンチマーク、および文書化された例を追加しました。

適切な単体テストを作成するために時間をかけることは、作成したコードまたはプログラムが期待どおりに機能し続けるという確信を高めるため、プログラマーとして役立ちます。 Goのtestingパッケージは、かなりの単体テスト機能を提供します。 詳細については、Goの公式ドキュメントを参照してください。

また、Goでのプログラミングについて詳しく知りたい場合は、チュートリアルシリーズ/無料の電子書籍、Goでのコーディング方法にアクセスしてください。