こんにちは、RPAの関口です。

最近週に一度、来年の新卒達と一緒にTDDをやりながらワイワイガヤガヤしております。そのなかで「プライベートメソッドのテストはどうすれば良いのか?」 という話題がありました。プライベートメソッドのテストについては
プライベートメソッドのユニットテストは書かないもの?
がよくまとまっていると思います。プライベートメソッドのテスト方法について考える中で「TDDの手順に従えばプライベートメソッドのテストがしたくなることは無い」のではないか?と思うようになりました。

プライベートメソッドはリファクタリングの結果現れる!

数値の配列を渡すと平均を計算して返してくれる機能を持ったクラス、AverageCalculatorを作りたいとします。平均計算の手順をまとめると以下のようになります。
  1. 配列の合計値を計算する
  2. 合計値を配列の要素数で割って返す
ここで「よし!まずは配列の合計値を計算する機能をつけよう!」と思って次のようなテストを書くと

calcSumメソッドの可視性について
  • 途中計算でしか使わない合計値の計算は、プライベートメソッドにしたい
  • でもプライベートメソッドだとテストのためにReflectionを使う必要があって面倒
という悩みが発生してしまいます。最初「平均値を返すメソッドを追加」しようと思っていたのに「合計値を返すメソッドを追加」しようとしたためです。平均値計算の手順はあくまで手順、初心に帰って平均値を返すテストを書きましょう。
そして合計値計算のメソッドは分離したいなぁと思ってリファクタリングする際に、calcSumという名のプライベートメソッドが自然と現れます。 このプライベートなcalcSumメソッドのためにテストを追加する必要はありません。中身がどのような実装になっていようと外部から呼び出されるcalcAverageメソッドが期待通りの結果を返してくれれば良いのです。またcalcSumメソッドはパブリックなcalcAverageメソッドを通じてテストされている、とも言えます。
 
パブリックメソッド経由でテストする
多くの場合、そのクラスのパブリックメソッド経由でプライベートメソッドのテストも同時に行えます。テストできているか不安があるならカバレッジを測定しましょう。

もしcalcSumの代わりに大変複雑な処理だった時はどうするの?

そのような場合、いきなりcalcAverage相当のメソッドを作るの事は難しいでしょう。一旦calcSumのメソッドの追加を行う必要が出てくるかと思います。もちろんテスト付きで。 でもそのcalcSumはAverageCalculatorクラスがやるべき事なのでしょうか?実はSumCalculatorという名の新しいクラスを作って、その中にパブリックメソッドとして書くのが正しいのではないでしょうか?
別クラスのパブリックメソッドとする
プライベートなメソッドのテストを書きたいということは、実はテスト対象の責務が多すぎることを示唆している場合があります。テストがどうしても書きたい場合は、その責務はテスト対象のプライベートな振る舞いでは無く、誰かのパブリックな振る舞いなのでしょう。テスト対象のプライベートメソッドを「クラスの抽出」や「メソッドの移動」を使って、テスト対象のコラボレータのパブリックメソッドとして抽出し、普通にパブリックメソッドとしてテストしましょう。

他の方法

ここまで書いておいてなんですが
  1. calcSumのテストと実装をパブリックで書く
  2. calcAverageのテストと実装をパブリックで書く
  3. リファクタリングでcalcSumメソッドの可視性をプライベートにして、calcSumのテストは削除
という手もあるかと思います。というかこちらのほうが自然ですね。せっかく書いたテストを捨ててしまうのは勿体無い気がしますが、実装中に書いたテストと最後に残すテストを同じにしなければならないというルールはありません。calcSumはcalcAverageを通してテストされているので良しとしましょう。

そんなこんなでプライベートメソッドのテストは必要ない!!・・・のではないかと最近思い始めました。