ひしだまの変更履歴

ひしだまHPの更新履歴。
主にTRPGリプレイの元ネタ集、プログラミング技術メモと自作ソフト、好きなゲームや音楽です。

先生、privateメソッドのテストがしたいです

2013-08-13 23:45:04 | PG(Java)

Javaでprivateメソッドをどうやってテストするかについて、不定期に話題にあがるんだけど。
「publicメソッドだけテストすればいい、privateメソッドをテストしたいというのはおかしい(設計(実装方法?)が間違っている)」という意見を聞いたことがあるが、違和感を持っていた。

で、「publicメソッドのテスト=仕様に基づくテスト=ブラックボックステスト」「privateメソッドをテストしたい=ホワイトボックステストをしたい」という事なのかもしれない、と思い付いてブログを書こうと思ってたんだけどDQ10が忙しくてさぼっていたら、
kencobaさんが『どうしてもprivateメンバをテストしたい人に対する、Unit4での解決案』でホワイトボックステスト・ブラックボックステストについて触れていた。(いやぁ、ブログをさぼると他の人が書いてくれて、ラッキー(爆))
その中で、「JUnitはブラックボックステストを前提としている」とあって、びっくり!なんと、そうだったのか!それならホワイトボックステストが出来なくても仕方ないなぁ
と思ったんだけど、ちょっと待って。後工程の結合試験や総合試験は、どうしたってブラックボックステストになるよね。先頭の単体試験(JUnit)でホワイトボックステストをしなかったら、どこでやるんだ…??

というところにタイムリーな記事が、渡辺 修司さんの『ブラックボックステストとホワイトボックステスト』。
「ユニットテスト(単体試験)はホワイトボックステストとブラックボックステストを組み合わせて行います」とのこと。
そりゃそーだよなー、ホワイトボックスとブラックボックスはテストの観点の話だから、工程とかあまり関係無さそうだもの。JUnitでホワイトボックステストをしても別に問題は無いわけだ。


ちなみに、自分がprivateメソッドのテストをしたいと思うのは、複雑な機能をメソッド分割して分離し、それぞれをテストしていきたいと思うから。
例えば以下の様なクラスを想定している。(どこぞのSIerっぽい雰囲気^^;)

public class ID0001Logic extends BusinessLogic {
  @Override
  public void execute(String key) {
    DAO dao = super.getDatabaseAccessObject();
    Record record = dao.selectByKey(key);
    record.setValue3(calculate(record.getValue1(), record.getValue2());
    dao.update(record);
  }

  private int calculate(int value1, int value2) {
    // 副作用の無いメソッド
    return ~; // 複雑な計算
  }
}

publicなのはexecuteメソッドだけだが、ロジックを切り出して、副作用の無いcalculateメソッドを作っている。calculateメソッドは他から呼ばれないので、普通の感覚ならprivateにするのは当然だろう。
でも、どう見ても、calculateをテストしたいと思うんだが、違うだろうか?
(もちろんexecuteメソッドもテスト対象だけど、テストパターンを色々実装する事を考えると、calculateメソッドを直接テストする方が遥かに楽) 


個人的には、こういうケースではpackage privateにする(可視修飾子を付けない)。

というか、オブジェクト指向はクラスを継承しメソッドをオーバーライドして部分的な修正を加えることで利便性(生産性)を上げるものだと思う。
が、フレームワークを作る人は可視範囲を制限しようとする。(カプセル化の考えからすれば当然なんだけど(苦笑))
フレームワークを利用する側(自分)としては、ちょっとだけ違う機能を加えたくて、オーバーライドできれば綺麗に作れるのに…ということが時々ある。(AntのCopyTaskとか、再利用したかったのに、出来ないし!)
そういう訳で、個人的にはprivateとかfinalとか使うの反対!(暴論w)
拡張する人の自己責任は当然なので、メソッドもフィールドも全部final無しのprotectedにしといてよ~(笑)

(先の例で言えば、calculateメソッドをprotectedにしておく(実際には、calculateメソッドが複雑であればさらにメソッド分割するはずなので、それらも全てprotectedにしておく)。すると、ID0001Logicを継承したサブクラスで一部のメソッドだけ変更して使える。これがprivateだと、コピペするしかなくなる(苦)(まぁ、どちらも自分の管理下にある場合は、オーバーライドしたくなった時にprotectedに変えればいいんだけどさ)) 


最後にもうひとつ、設計とテストの関係について。
テストは、設計書に書かれた内容が実現されているかを確認する為に行うものだと思う。
「要件定義書に書かれていることを確認する→総合試験」
「基本設計書に書かれていることを確認する→結合試験」
「詳細設計書に書かれていることを確認する→単体試験」といった感じ。
つまり、テストは本質的にブラックボックステストになる。

ここにもうひとつ、「プログラム設計書の確認→単体試験」という関係があると思っている。
プログラム設計書は、メソッドからロジックまで全部書かれている想定。しかし現在のプログラミング技法では、プログラム設計書は書かず、プログラムを直接記述する。
だから、この単体試験は、本来は「プログラム設計書を元とするブラックボックステスト」なのだが、実際は「プログラム設計書=プログラム」なので、プログラムを見て試験項目を抽出したテスト、すなわちホワイトボックステストになる。
言い直すと、「ホワイトボックステストは、プログラム設計書を元とするブラックボックステストである」。 

とも考えられると思うんだけど、どうかなぁ?

そしたら、privateメソッドも(本来はプログラム設計書に書かれているのでブラックボックステストの)テスト対象であり、「privateメソッドをテストしたいというのはおかしい」という話にはならないと思う。



最新の画像もっと見る

4 コメント

コメント日が  古い順  |   新しい順
Unknown (渡辺修司)
2013-08-14 19:52:56
反応有り難うございます、定期的に出る話題ですね。
ちょっとだけコメントさせてください。

この例におけるcalculateメソッドですが、自分もユニットテスト対象として、おそらくは、妥当な粒度だと思います。
確かにpublicなメソッドはexecuteだけで、残りはprivateメソッド的に扱うわけですが、考え方次第ではcalculateメソッドはpublic的に扱えると思います。

というのも、calculateメソッドは「計算する」という責務を持っているので、独立したクラスになってもおかしくありません。
そもそも、executeメソッドというのが責務が大きすぎて問題、と考えます。
勿論、フレームワークの制約などもあり、窓口として巨大な責務を持ったpublicメソッドがあるは仕方有りません。
このようなメソッドは、自分ならば、ユニットテストの対象とはせずに、結合テストの対象としてしまいます。

calculateメソッドですが、デフォルトスコープのCalculaterクラスに昇格させた上で、CalculaterTestでユニットテストすれば自然ではないでしょうか?
コーディング規約の制約などで、勝手にクラスが作れない等もあります。
そのような場合は、デフォルトスコープ等にしてお茶を濁すしかないと思いますが…。

まとめると、
・executeメソッドは責務が大きすぎるのでユニットテストの対象としては微妙
・calculateメソッドこそユニットテストの対象としてちょうど良い
・テストの粒度にあわせてクラス設計すると、うまくいくことが多い

参考になれば幸いです。
(もしかしたら、ブログでネタにするかもしれません^^)
返信する
Unknown (小林)
2015-10-14 00:56:44
ttp://otndnld.oracle.co.jp/products/jdev/pdf/905/J-gettingstartedwithunittesting.pdf

引用>
ホワイトボックス・テストを書かない

ユニット・テストでは、クラスがどのように実装されるかではなく、クラスが何を行うかに重点を置く必要があります。クラスのホワイトボックス・テストは、private および protected メソッドもすべてテストすることになり、それによる実装の変更によって、テストも変更が必要になる可能性が高くなります。したがって、既存のテストを使用して何も破損しなかったことを実証する場合、ホワイトボックス・テストは効果的なリファクタの妨げになります。同様に、インタフェースの実装クラスではなく、インタフェースに対してテストを記述することをお薦めします。
返信する
Unknown (小林)
2015-10-14 01:23:17
UnitTestはあくまでclassのinterfaceの動作を保証するもので、classの内部構成を保証するものでは有りません。あくまでclassのinterfaceを通して動作が正しいか保証する事で、リファクタリング前後の振る舞いが変わっていないことを保証でき、大胆なリファクタリングを可能にしてくれます。


そういったリファクタリング観点で見ていくとprivate methodに対するUnitTestの考え方はどんどん変わっていくと思います。
またprivate methodの作りに関する考えも変わり、極めて単純か、将来破棄しそうな一時的なmethodで無いとprivate methodにしないようになってきますよ。
返信する
Unknown (師子乃)
2018-10-31 20:57:03
初めまして。

勉強させていただきます!
返信する

コメントを投稿