エキスパートチームのtenntennです。Fridayという社内勉強会を開催しています。毎週やっているとそれなりに知見が溜まってくるので、定期的に"こぼれ話"としてブログを書こうという話になりました。Fridayで話題にあがった非公開な機能を使ったテストについて扱いたいと思います。Go"(スライド/動画)が参考になります。テーブル駆動テストやテストヘルパーなど非常に勉強になるので、まだ見たことのない方はぜひスライドや動画をご覧ください。TL;DRGoのテストではテスト対象とテストコードを別パッケージにできる別パッケージにすると非公開な機能(パッケージ内にしか公開されていないメソッドや変数など)にアクセスできないテスト対象と同じパッケージのexport_test.goというファイル経由で非公開な機能をテストコードにだけ公開するパターンがあるテストコードのパッケージGoでは基本的には1つのディレクトリ内のコードは、1つパッケージで構成されている必要があります。テストの場合は例外で、次のようにmypkgパッケージのテストコードはmypkg_testでも問題ありません。}}もちろん、テスト対象のコードとテストコードのパッケージは同じにすることは可能です。それでもテスト対象とテストコードのパッケージを別にする利点はどこにあるのでしょうか。考えられる利点としては、パッケージを分けることによって、テスト対象とテストコードが疎結合にすることができるという点が挙げられます。疎結合にすることによって、テストコードはあくまでもテスト対象のパッケージのユーザという立場でテストを書くことができます。することで、テスト対象パッケージを利用する側の視点から見ることができ、「利用しやすいAPIにするには?」ということを考えながらコードを書くことができます。このような理由から、私はテスト対象とテストコードのパッケージを分けることができるのであれば、積極的に分けるべきだと考えています。非公開(unexported)な機能のテストテスト対象とテストコードのパッケージは分けたほうがよいという話をしました。してもパッケージを一緒にしたい場合があります。非公開な変数の値を変えたり、非公開なメソッドを呼び出したりする場合です。testでは、ビルド対象のファイルが違います。buildでは、_test.goのサフィックスを持つテストファイルはビルド対象から外されます。そのため、export_test.goなどの名前をつけ、パッケージ名をテスト対象と同じにすることでテストの時のみにアクセスできる変数や関数を作ることができます。次のような構成のパッケージを考えます。export_test.gomypkg.goには、次のようにmaxValueという定数があった場合に、テストのときだけこの値を参照したいとします。100export_test.goで次のように定義することで、maxValueはExportMaxValueとしてテストコードに公開されます。maxValueテストコードでは次のように公開された定数を参照することができます。testの際にしかビルドされないため、通常のビルドにはExportMaxValueは含まれず、テストコード以外からは参照できません。このようにexport_test.goで公開する箇所を限定することで、想定外の使い方をされることを防ぐことがきます。このexport_test.goを使ったパターンは、実際に標準パッケージであるnet/httpパッケージやreflectパッケージで用いられています。export_test.goというファイル名のexportの部分には特に決まりはありませんが、標準に習ってexport_test.goという名前にしておく方が分かりやすいでしょう。それでは、このexport_test.goを用いた非公開な機能を使ったテストの面白いパターンを見ていこうと思います。非公開な変数の値を変更するテストの際に変数の値を変えたくなることがあります。次のようにサーバのURLが設定したあった場合を考えます。"https://example.com/api/v2"このURLをテストのときだけ変えたいとします。export_test.goに次のように書くことでSetBaseURL関数を使えばbaseURLを変更することができるようになります。SetBaseURL関数は、引数で受け取ったURLをbaseURLに設定します。SetBaseURLは戻り値としてbaseURLを元に戻す関数を返します。テスト時にはbaseURLを"http://localhost:8080"に変えておき、テスト関数が終了する直前にbaseURLを戻すということができます。以下にテストコード}非公開なメソッドの呼び出し次のようなテスト対象コードがあった場合に、Counter型のresetメソッドをテストで呼び出したいという場合を考えます。0}メソッドは、次のようにメソッドバリューとして変数に入れることができます。reset()}また、次のようにレシーバを束縛していなくても変数に入れることができます。*Counter)のように、レシーバが第1引数になります。引数があるメソッドの場合は、レシーバが第1引数に、第1引数が第2引数に、のように1つずつずれる形になります。レシーバが束縛されてないので第1引数で指定する}さて、これを踏まえて次のようにexport_test.goに書くことでメソッドを公開することができます。(*Counter).reset第1引数に*Counter型の値を指定することで呼び出すことができます。export_test.goはテスト対象のパッケージと同じパッケージであるため、次のようにメソッドを追加することができます。c.n}フィールドの値を変えたい場合は、次のようにセッターを作ればよいでしょう。n}非公開な型を使用するテスト対象のパッケージに次のような型が定義してあり、この型をテストで使用したいとします。`json:"value"`}Go1.9から入った型エイリアスの機能を使うと型に別名をつけることが可能です。次のように、エイリアス名を大文字から始めることでテストコードへ公開することが可能です。responseこうすることでresponse型とExportResponse型は完全に同じ型として扱われるため、キャストする必要なく引数や変数に入れることができます。フィールドやメソッドについても、元の型と同じように利用できます。クライアントライブラリのテストここまで非公開な機能を使ったテストの手法を説明してきました。最後に、具体的な例を紹介したいと思います。次のようなクライアントライブラリを作成しているとします。Client型がありそのGetメソッドを呼ぶとサーバにリクエストが飛びます。リクエストパラメータとしてnを渡していて、レスポンスはJSONで受け取っています。長くなるので細かなエラーハンドリングはサボっていますが、本来ならステータスのチェックなどをするべきでしょう。nil}さて、このClient型のテストを作っていきましょう。次のようにnet/http/httptestパッケージを使ってモックサーバを立てて、そこに期待したリクエストが来るかどうかチェックしてみたいと思います。}}テストを実行してみましょう。実行するとテストが落ちてしまいました。https://example.com/api/v2/getにアクセスしようとして失敗したようです。このURLでアクセスしてしまうとhttptest.NewServerで作ったモックサーバにアクセスすることができません。0.031shttptest.ServerにはURLというフィールドがあるため、そちらをbaseURLに設定すれば良さそうです。baseURLへの設定は、"非公開な変数の値を変更する"で紹介したように、export_test.goに関数を用意します。}}SetBaseURL関数は、テストコードからしかアクセスできない関数で、引数で渡した値をbaseURLに設定します。戻り値は、設定した値を元に戻す関数で、これを呼び出すことでbaseURLを元の値に戻すことができます。ここで注意点としては、SetBaseURLは複数のゴルーチンから呼ばれることは期待していないため、テストを並行に実行する場合には注意が必要です。SetBaseURL関数は次のように使います。mypkg.SetBaseURL(s.URL)()のようにdeferで呼び出すことで、サブテスト関数が終了した際にbaseURLの値を戻し、次のサブテストに移ります。0.030sうまくテストを実行することができました。`{"value":"hoge"}`)の部分が気に入りません。文字列リテラルで指定するのではなく、型を使ってJSONを生成したいと思います。レスポンスを表すgetResponse型は次のように定義されていて、公開されていません。`json:"value"`}これを公開するには、export_test.goで次のように記述する必要があります。getResponseそうすると、テストコードを次のように変更することできるようになります。}}前述の例と比べると、JSONを生成する部分が次のように変更されています。ExportGetResponse型を用いてJSONが生成されていることがわかります。Fridayで話題にあがった非公開な機能を使ったテストについて解説しました。#17でも発表する予定です。この記事を読んで疑問に思った点がある方は、ぜひgolang.tokyoに参加して質問をしてみてください。Fridayでは今回のようなネタを「あーでもない、こーでもない」とメルカリのGopher(Goのエンジニア)で集まってわいわいと話をしています。採用情報メルカリは常に優秀なソフトウェアエンジニアを募集しています。ご興味を持たれた方はぜひご応募ください。skills月別アーカイブスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。