QA@IT

プライベートメソッドのユニットテストは書かないもの?

16743 PV

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つの方法があります。

  1. パブリックメソッド経由でテストする
  2. 別クラスのパブリックメソッドとする
  3. テスト対象の可視性を(やや)上げる
  4. プライベートのまま、リフレクションでアクセスしてテストを書く

パブリックメソッド経由でテストする

多くの場合、そのクラスのパブリックメソッド経由でプライベートメソッドのテストも同時に行えます。テストできているか不安があるならカバレッジを測定しましょう。

別クラスのパブリックメソッドとする

プライベートなメソッドのテストを書きたいということは、実はテスト対象の責務が多すぎることを示唆している場合があります。テストがどうしても書きたい場合は、その責務はテスト対象のプライベートな振る舞いでは無く、誰かのパブリックな振る舞いなのでしょう。テスト対象のプライベートメソッドを「クラスの抽出」や「メソッドの移動」を使って、テスト対象のコラボレータのパブリックメソッドとして抽出し、普通にパブリックメソッドとしてテストしましょう。

テスト対象の可視性を(やや)上げる

例えば Java では、同一のパッケージからのみアクセスできる「パッケージプライベート」と呼ばれる(正式名称ではなかったかもしれませんが)可視性があり、テストを同一パッケージに記述することでテストからはアクセスできるような設計を行ったりします。(ただし、この質問の場合 JavaScript なので、この手段はとれませんね)

プライベートのまま、リフレクションでアクセスしてテストを書く

リフレクションは最後の手段であり、強力な手段でもあります。プロダクトコードに手を入れることができない状況や、レガシーコード(テストコードの無いコード)に対する「仕様化テスト(Characterization Test)」を書いているような状況では、リフレクションは唯一の、かつ強力な手段になります。プライベートメソッドにテストを書くことのデメリットを理解しつつ、黒魔術の強力さを堪能しましょう。


繰り返すと、プライベートなメソッドや関数をテストする必要は無いと考えています。プライベートなメソッドは、実装の詳細であるからです。自動テストを書くモチベーションの一つとして「リファクタリングの支えになる」ことが挙げられますが、リファクタリングとは簡単に言うと「外部から見た振る舞いを変えずに内部の実装をきれいにすること」です。外部から見た振る舞いは、多くの場合自動テストで検証されます。しかし、プライベートメソッドに対するテストは内部の実装に対するテストになってしまうことが多く、そして内部の実装に対するテストはリファクタリングの妨げになりがちです。自動テストの助けを借りて積極的にリファクタリングを行いたいのに、その自動テストがリファクタリングの妨げになる。これはとても皮肉な状況です。避けられれば避けたいものです。

テスト「できる」ことと「するべきである」ことは異なります。リフレクションを使えばプライベートなメソッドのテストは「できる」のですが、そのテストはやがて実装改善の邪魔になりかねません。

編集 履歴 (0)
  • 詳しい解説、ありがとうございます。可視性とテストすべきかどうかは独立した話だと思えたのですが、そうでもない気がして来ました。 -

今さら書くほどの内容でもないですし、質問が「書かないもの?」という聞き方になっており、そもそも「べき論」でないのを踏まえたうえで、自分なりの基準を残しておきます。

テストした方がよいと感じるなら必要なテストコードを書けばよいと私も思います。その際、「テストした方がよい」と判断したのであればそれを優先して考えます。

組み方が原因(メソッドが長大であるとか)でテストが書きにくくなってしまっているものに対しては頑張って工夫して書きましょうが回答になると思うのですが、言語の機能的にそもそも書きにくいものを頑張ってテストするのってなんか本末転倒じゃないか?と自分は感じています。TDDのテストコードならなおさらそこに時間を取られるのはもったいないと思います。すでに書かれている通り、publicなメソッドを通してテストするなど他に方法があり、それで十分と判断できるならわざわざprivateメソッドのテストはしません。

他に方法がないけれどどうしてもそのメソッドを単体でテストしたい場合はテストしやすさを優先し、publicにします。 自分の場合はそれが恐らく最もコストが小さいからです。 テスト用のサブクラスで可視性を変更することができるのであればそれでもよいかもしれません。もし可視性の変更が不可能な事情があるのであれば他の方法を考えます。

くり返しますが、自分の場合のポイントはテストした方がよいと判断したならテストしやすさを優先して考えることと、コストの小さい方法を選ぶ、という感じですね。変に面倒な方法でテストしてるとあとで読む時につらいですし。(ちょくちょくつらくなります)

編集 履歴 (0)
  • 実体験に基づくご意見、すごく参考になりました。コストのバランスですね、なるほどです。 -

私の理解では、プライベートメソッドは

  • パブリックメソッドに対して機能を提供している
  • 普通は、複数のパブリックメソッドから利用される

なので、プライベートメソッドをテストするのはごく自然だと思います。

つまり、プライベートメソッドが提供している(限定的に再利用可能な)1つの小さな機能をテストするのです。

メリットとしては、「コーナーケースをきちんとテストできていることを見やすい」などがあると思います。
また、最終的には残らないとしても、実装の途中段階でそういうテストを書くのも有効だと思います。

「別クラスのパブリックメソッドにする」のは、そのメソッドをいろんなクラスから利用したいならばそうすべきだと思いますが、そうでないなら、元のクラス内に留めておいた方が良いと思います。
(こういうのを大クラス主義というのかな。違うかな。)

プライベートメソッドのテストがリファクタリングを邪魔するとは思いません。
オブジェクトのパブリックな振る舞いを変えたくないのであれば、パブリックメソッドに対するテストがあれば良いはずです。
(もちろん、あるプライベートメソッドの振る舞いを変更するならば、それに対するテストも変更することになると思います。)

様々な粒度のテストがあって、プライベートメソッドに対するテストは最も粒度の小さいものです。

完璧なインテグレーションテストがあれば、ユニットテストは必要ないでしょうか?
そういう考え方もあるかもしれませんが、個人的にはそうは思いません。
(そもそも完璧などないし。)

同じように、プライベートメソッドに対するテストが有効な場面はいろいろとあると思います。
(パブリックメソッドに対するテストは当然書くとしても。)

これは、そういう類のリフレクション機能の存在理由の1つで、邪悪な使い方ではないと思います。
(リフレクション機能に感謝!)

そういう機能が利用できない場合は、パブリックメソッドに対するテストで満足することになるのかな。
テストのために可視性を変更したり新しいクラスを作ったりするのは、特別な理由がない限りは避けたい気がします。

パブリックメソッドに対するテストを通じてプライベートメソッドがテストされるのはその通りかもしれませんが、それでも、ちょっとした(非自明な)機能を提供しているプライベートメソッドに対するテストはしばしば有効だと思います。
(すべてのプライベートメソッドに対してテストを書くべきだとは思いませんが。)

まとめると、

  • 書いた方が良い気がする (「そのプライベートメソッドが提供しようとしている機能がちゃんと実現されているか不安」とか「そこには絶対にバグがあってはならない」とか)
  • 書くことができる (そういう機能が提供されている)

ならば、書いたら良いのではないでしょうか。

クラスやオブジェクトを最小単位とみなすと、その実装の詳細であるプライベートメソッドをテストするのは不自然かもしれませんが、メソッドを最小単位として考えると、(可視性にかかわらず)テストを書くのは不自然ではないと思います。
「外側の仕様から決めていくアプローチ」と「よくテストされた小さな部品を組み合わせていくアプローチ」の違いかもしれません。

ここで言っている「部品」というのは、クラスやオブジェクトではなく、メソッドや関数のことです。
結局、小さなクラスをたくさん作るのが好きではないというだけかもしれません。
あるいは、オブジェクト指向に関数型のアプローチを取り入れていく、ということかな。(違うかな。)

追記:

気になったので Ruby のソースに付いているテストコードをざっと見てみたところ、例えば Net::HTTP の parse* のようなプライベートメソッドがテストされていました。
どのような意図かはわかりませんが、上で言おうとしたことの良い例になっているのではないかと思います。

編集 履歴 (1)
  • 分かる気がします! 「クラスの振る舞い」より、もっと粒度を下げたい、いやいや、だからってクラスを分けるのってなんか違う、という感じでしょうか。ご意見ありがとうございます -

JSに限らずRubyでもそうしますが

  • 最初にすべてのメソッドをpublicにしてテストを書いて実装を行います
  • 次にpublicにする必要が無いメソッドをprivateにします。これに伴ってテストも削除します
  • 開発時に書くテストと残すテストは必ずしも一致しなくていいのかなと思っています

Rubyだとsendを使って残すこともあります

編集 履歴 (0)
  • ありがとうございます。そう、開発時に書くテストと残すテストは、書く目的が違うのではないかというのが、そもそもの疑問の発端にありました。 -

private methodのテストであっても小細工することなく、public methodと同様にテストを書ける仕組みがあるならば書いても問題ないと思います。

テストコードから通常の手段でprivate methodを呼べないのは単なる言語上の制約であって、テストすべきかいなかとはまったく関係のない話ではないでしょうか?

そもそものprivate/publicの概念は通常のプログラミングを行う上でカプセル化や疎結合を実現するためのもので、テストの時のことは言語設計の時点では考慮されていない気がします。

しかし言語やツールにそういう仕組みが無いのにprivate methodをテストするためだけにコードに手を入れるのには反対です。

テストを行う場合は他の回答にあったようにリフレクションを使うなりして対象となるコードには手をいれずprivate methodのままテストするのがいいと思います。

編集 履歴 (0)
  • ご意見ありがとうございます。私の疑問のスタート地点も、言語上の制約とテストするべきかどうかは独立した話ではないかというところです。ただ、「実装改善の邪魔」になるというのは、ややありそうなのかなと思いました。テストのためにコード本体を改変するのは良くなさそうですよね。 -

結論は出てるでしょうが補足です。

http://otndnld.oracle.co.jp/products/jdev/pdf/905/J-gettingstartedwithunittesting.pdf

ホワイトボックス・テストを書かない
ユニット・テストでは、クラスがどのように実装されるかではなく、クラスが何
を行うかに重点を置く必要があります。クラスのホワイトボックス・テストは、
privateおよびprotectedメソッドもすべてテストすることになり、それによる実装
の変更によって、テストも変更が必要になる可能性が高くなります。したがって、
既存のテストを使用して何も破損しなかったことを実証する場合、ホワイトボッ
クス・テストは効果的なリファクタの妨げになります。同様に、インタフェース
の実装クラスではなく、インタフェースに対してテストを記述することをお薦め
します。

この記事以外にもあるのですが、有名どころの人が書いてるのはprivateな関数はTestするなです。
あくまでRefactoring時に仕様が維持されているかの確認用ですから。

編集 履歴 (0)

t_wada様の回答でほぼ尽くされている気がしているので、完全に蛇足ですが。

クラス(およびそれに付随する可視性とリフレクション)の概念のないJSで、knsmr様の元々書こうとしていたテストコードを記述するためのトリッキーなライブラリを作っています。
https://github.com/xorphitus/junc

安定版とは言い難いのですが、最終手段としてもしかしたらお役にたてるかもしれません。

ただやっていることがかなり強引なので、t_wada様の考え方に沿って「テストしない」ともっていった方がよいのかもしれませんね。

編集 履歴 (0)
  • あれ、リンクの貼り間違え? と、思いましたが、コメントとしてテストコードを入れてしまうということですね。minifyのプロセスに取り込めれば、こういうのもありでしょうか。ありがとうございます。 -
  • おっしゃる通りコメントで埋め込むというものです。
    説明が不十分で、READMEもアレですね。申し訳ありません。

    ブラウザから実行する作りにしていますが、やはりminifyにもっていく等してコード中にこういうのが残らない形にしたいですよね…。
    -
  • あ、スミマセン。ブラウザで直接叩くんですね。なるほど。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。