この文章の背景
この文章はプライベートメソッドのテストを書くべきか否かに関する knsmr さんのご質問に対して 2013/03/13 に QA@IT で回答したものです。残念ながらQA@IT のサービス終了(2020/02/28)と共にアクセスできなくなってしまったため、運営を行っていたアイティメディア株式会社様、開発を行っていた永和システムマネジメント様、そして質問をされた knsmr さんに許可とご協力をいただき、当時の回答をサルベージしてブログに転載する運びとなりました。
プライベートメソッドのテストはよく議論になるテーマですので、当時の回答を再編集し、knsmr さんのご質問も含め、ご利用いただきやすいライセンス CC BY(クリエイティブ・コモンズ — 表示 4.0 国際 — CC BY 4.0) で公開いたします。
目次
knsmr さんのご質問
JavaScript を書いています(ブラウザがターゲットです)。手動テストが面倒になって、 Jasmine を使ってテストを書きはじめています。
オブジェクトに含まれる関数(プロパティ)の数が増えてきたので、外から呼ばれることがないものはプライベートメソッドのように扱おうと、クロージャを使って、以下のように書きました。
var Foo = function(foo) { this.foo = foo; } Foo.prototype = (function() { var _privateMethod = function(data) { // some code }; return { constructor: Foo, fuga: function(a) { var data = _privateMethod(a); // do some stuff return data; }, paga: function(b) { var data = _privateMethod(b); // do some stuff return data; } }; })();
これはこれでいいのですが、_privateMethod
が外から見えないのでユニットテストも書けなくなりました。
こういう場合どうするのかなと思って検索してみたら、そもそもprivateとして隠蔽している以上、それは実装詳細なんだからユニットテストなんてしないんだよ、という意見がありました。そういうものなのでしょうか?
いまの私の場合、テストを書く理由として「先にテストケースを5つか6つほど並べておいて、実装した瞬間に実装が正しいことを確認したいだけ」というTDD的な発想でいました。やりたいことはハッキリしていて、あるインスタンスからなる配列を入れたら、ゴニョっと処理した結果のNumberの配列が返ってくる関数がほしい、具体的には入力がこうで出力がこう、というような程度の話です。どうせREPLやconsole.log (printf) で試したり、コメントに入出力の例を残すのだし、だったらそのままテストコードとして書いちゃいたいな、ということです。
Rubyだとprivateなメソッドでも明示的にレシーバーにメッセージとしてメソッド名を送れば呼び出せるので、こういう迷いはないと思うのですが、一般論としてはユニットテストを書く・書かないということはメソッドの可視性から必然的に決まってくるものなのでしょうか? 言語によってはリフレクションのAPIで頑張るとかなのでしょうか?
私の回答
短くまとめると、プライベートなメソッドのテストを書く必要は 無い と考えています。
ほとんどのプライベートメソッドはパブリックメソッド経由でテストできるからです。プライベートメソッドは実装の詳細であり、自動テストのターゲットとなる「外部から見た振る舞い」ではありません。
ただし、この議論にはプロダクトコードもテストコードも自分で書いていることという前提があります。プロダクトコードに手を入れられず、テストコードも無いレガシーコードに対しては、リフレクションは強力な手段です。
プライベートなメソッドのテストに関しては、4つの考え方があります。
- パブリックメソッド経由でテストする
- 別クラスのパブリックメソッドとする
- テスト対象の可視性を(やや)上げる
- プライベートのまま、リフレクションでアクセスしてテストを書く
パブリックメソッド経由でテストする
多くの場合、そのクラスのパブリックメソッド経由でプライベートメソッドのテストも同時に行えます。テストできているか不安があるならテストカバレッジを確認しましょう。
別クラスのパブリックメソッドとする
プライベートなメソッドのテストを書きたいということは、実はテスト対象の責務が多すぎることを示唆している場合があります。テストがどうしても書きたい場合は、その責務はテスト対象のプライベートな振る舞いではなく、他の誰かのパブリックな振る舞いなのでしょう。テスト対象のプライベートメソッドを「クラスの抽出」や「メソッド/関数の移動」を使って、テスト対象のコラボレータのパブリックメソッドとして抽出し、普通にパブリックメソッドとしてテストしましょう。
テスト対象の可視性を(やや)上げる
例えば Java では、同一のパッケージからのみアクセスできる可視性があり(正式名称ではありませんが「パッケージプライベート」と呼ばれます)、テストを同一パッケージに配置することでテストからアクセスできるような設計を行うことがあります。(ただし、この質問の場合は JavaScript なので、この手段はとれません)
プライベートのまま、リフレクションでアクセスしてテストを書く
リフレクションは最後の手段であり、強力な手段でもあります。プロダクトコードに手を入れることができない状況や、レガシーコード(テストコードの無いコード)に対する「仕様化テスト(Characterization Test)」を書いているような状況では、リフレクションは唯一の、かつ強力な手段になります。プライベートメソッドにテストを書くことのデメリットを理解しつつ、黒魔術の強力さを堪能しましょう。(ただし、この質問の場合は JavaScript なので、この手段はとれません。JavaScript は比較的緩い言語ですが、クロージャの情報隠蔽は非常に強固です)
まとめ
繰り返すと、プライベートなメソッドや関数をテストする必要は無いと考えています。プライベートなメソッドは、実装の詳細であるからです。
ホワイトボックステストを書きたくなるのは、テストの問題ではなく、設計の問題だ。コードがきちんと動いているかどうかを変数を使って確かめたくなるときは、設計を改善する機会であると私は考えている。不安に負けて変数をチェックしてしまえば、改善の機会は失われる。
『テスト駆動開発』 第29章 xUnitのパターン p.226
自動テストを書くモチベーションの一つとして「リファクタリングの支えになる」ことが挙げられますが、リファクタリングとは簡単に言うと「外部から見た振る舞いを変えずに内部の実装をきれいにすること」です。外部から見た振る舞いは、多くの場合自動テストで検証されます。
しかし、プライベートメソッドに対するテストは内部の実装に対するテストになってしまうことが多く、そして内部の実装に対するテストはリファクタリングの妨げになりがちです。自動テストの助けを借りて積極的にリファクタリングを行いたいのに、その自動テストがリファクタリングの妨げになる。これはとても皮肉な状況であり、避けられれば避けたいものです。このような状況は「構造的結合が強い」と表現されます。
プログラマーのテストは、振る舞いの変化に敏感であり、構造の変化に鈍感でなければいけない。つまり、プログラムの振る舞いが安定しているように見えるなら、テストを変えるべきではない。
テスト「できる」ことと「すべきである」ことは異なります。リフレクションを使えばプライベートなメソッドのテストは「できる」のですが、そのテストはやがて実装改善の邪魔になりかねません。
この文章は クリエイティブ・コモンズ — 表示 4.0 国際 — CC BY 4.0 の下に公開されています。