27
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Go 5Advent Calendar 2020

Day 5

golangで非公開関数をテストするためのよくあるテクニック

Posted at

こちらはGo 5 Advent Calendar 2020の5日目の記事です。

こんにちは、うえちょこ(uechoco)です。
DeNAから事業統合で株式会社Mobility Technologiesに転籍しまして、バックエンドエンジニアをしております。
ええ、あの「GO」という名前のタクシー配車アプリを「Go」で作っている会社です 。(これが言いたかったw

最近、プルリクのコメントで今回紹介するテクニックの1つをつぶやいてみたら、意外と知られていなかったので、書いてみようかなと思った内容です。

まずはテストに関するおさらい

_test.go ファイル

_test.go というsuffixのファイルにすると、これらのファイルに書かれたコードを通常のビルドから除外してくれます。

余談ですが、 Test関数以外にも、Benchmark関数だったり Example関数 だったりもこのファイルに書きます。

_test パッケージ

Goでは1つのディレクトリ内のコードは1つのパッケージに所属するようになっていますが、例外として _test パッケージは置くことができます(例: fmt パッケージに対する fmt_test パッケージ)。
別のパッケージにすることで、テスト対象のパッケージを使う(importする)側に立って関数を実行するので、公開されている関数が期待通りに動くかどうかを確認することができます。

一方で _test パッケージを用いると、非公開な関数や非公開なフィールド変数にテストコード側からアクセスすることができなくなります。非公開なコードをテストで使いたい、テストしておきたいときに困ります。

そんなときによく見かけるテクニックが以下の2つです。

テクニック1: export_test.go を使う

export_test.go というファイルを置いて、テストコードのための特別な実装をしておくというテクニックです。これは golang本体でも頻出です。

いくつかの記事で紹介されています。

非公開関数のテストコードを書く例

golang本体の事例を簡単に書いておきます。

func parsenum(s string, start, end int) (num int, isnum bool, newi int) {
    // 省略
}
  • https://github.com/golang/go/blob/master/src/fmt/export_test.go
    • テスト対象と同じパッケージ名で、非公開関数の公開エイリアスを定義しています。
    • _test.go ファイルなので、テスト用ビルドでのみ使用できます。
    • こうすることで、テスト用ビルドのときだけ拡張され、かつテストコードは公開・非公開問わず _test パッケージに書くことができます。
package fmt

var Parsenum = parsenum
func TestParsenum(t *testing.T) {
	testCases := []struct {
		// 省略
	}{
		// 省略
	}
	for _, tt := range testCases {
		num, isnum, newi := Parsenum(tt.s, tt.start, tt.end)
		// 省略
	}
}

その他の例

export_test.go は、非公開関数の公開エイリアスの定義だけではなく、テスト用にごにょりたい用の関数をおいておく場所としても利用されます。例えば非公開フィールドにアクセスするための関数を生やしたりすることにも使われています。

テクニック2: _internal_test.go を使う

こちらは先程のテクニックよりマイナーかもしれません。
テスト対象と同じ( _test ではない )パッケージの中にテストコードを書いているという目印として、ファイル名の末尾に _internal_test.go を付けようというルールです。

いくつかの記事で紹介されています。

golang本体では1箇所だけ使用されています。

transport_internal_test.go
package http
// 省略
func TestTransportPersistConnReadLoopEOF(t *testing.T) {
	// 省略
	tr := new(Transport)
	// 省略
	pc, err := tr.getConn(treq, cm)
	// 省略
}
// 省略

こちらは同じパッケージ内にテストコードを書くので、 export_test.go と違って公開用エイリアスの定義を挟むことはありません。直接テストコードの中から非公開関数を呼び出すことができます。私はテスト用に便利関数を生やしたりといったケースにあまり遭遇してこなかったので、いまのところは export_test.go よりは _internal_test.go をよく使います。

余談ですが、とあるgoのOSSのissueで、「内部テストを書きたいから _internal_test.go を使っていこう」というissueに対して「マイナーなルールだから受け入れられない」とメンテナが判断してcloseされてしまっていたケースを見かけました。

ところでlinterで怒られないの?

golangci-lintでサポートされているlinterの1つにtestpackageがあります。
これはテストが _test パッケージ以外に書かれているときに教えてくれるlinterです。

このlinterのデフォルト挙動では、 XXX_internal_test.goexport_test.go といったファイル名の場合はスキップするように設定されています。今回紹介したテクニックを使っても nolintコメントを記述する必要はありません。

おわり

27
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?