💭

「privateだからテストしない」は思考停止

に公開

「privateメソッドにはテストを書くな」

有名な原則です。私も基本的には賛成です。

しかし、原則に縛られて、本当に必要なテストを諦めていませんか?

publicとprivateの違い

publicメソッドは、概念への窓口——外部に公開された安定したインターフェースです。当然、それに対するテストも安定します。

一方で、privateメソッドは内部実装の詳細です。リファクタリングで増減しますし、publicメソッドの変更にも影響を受ける不安定なものです。それに対するテストは当然、不安定なものとなります。

不安定なテストが招く実害

不安定なメソッドに対するテストは、メンテナンスコストが高くつきます。

リファクタリングしたいのに、テストの書き換えコストが重すぎて断念する。そんな状況は避けるべきです。

だから「privateメソッドはテストしない」が原則になるのです。

共用ロジックというジレンマ

複数のpublicメソッドから呼ばれる共用ロジック。public経由でテストすれば、同じ検証を複数箇所で繰り返すことになりかねません。

共用ロジックを直接テストすれば、一箇所で済みます。

しかし、先述の通り不安定なテストのコストは高くつきます。privateメソッドを直接テストするなら、本当にそれが必要か吟味しなければなりません。

「別クラスに切り出せ」への疑問

「テストしたいなら、別クラスのpublicメソッドにしろ」これはよく耳にする解決法です。

もちろん、設計上自然な分割であれば切り出すべきです。単一責任原則に沿った分割は歓迎されます。

ところが、テストを書くためだけにクラスを分割するのは話が違います。凝集度の高いクラスが分割される。privateだったロジックが、別クラスとはいえpublicになる。どちらも避けるべきことです。

テストの都合でプロダクトコードを歪めることは、本意ではないはずです。

privateなインターフェース

privateメソッドはインターフェースではない——普通はそう考えます。

しかし「独立してテストを書きたい」と強く思うprivateメソッドは、複数のpublicメソッドから共有される「内部概念」への窓口である可能性があります。

その内部概念が重要であれば、publicメソッドと同じ慎重さで設計する価値があります。その結果、そのprivateメソッドは「安定した内部インターフェース」になります。それに対するテストもまた、安定するでしょう。

これは「別クラスに切り出せ」と本質的には同じことをしています。整理する、ただし分割と公開はしない——それだけの違いです。

原則と例外

原則を守るのは大事です。でも、「なぜ」を理解せずに従うのは思考停止です。公開範囲だけを理由に、真っ当な判断を諦める必要はありません。

もっとも、これが当てはまるケースは多くありません。ほとんどのprivateメソッドは、ただの実装詳細、もしくは独立したテストを書く必要がない内部概念です。

例外が許されるのは、以下の条件をすべて満たす場合に限ります。

  • 独立したテストを書きたいと強く思う
  • クラスにすべきではない内部概念である
  • コードを整理して安定したインターフェースにできた

一つでも欠ければ、そのテストは書くべきではありません。

また、これらの判断には経験が要ります。見極められる人がいて、コードレビューが機能している——その環境がなければ、全面禁止が正解です。

「privateだから」で止まるな。その先を考えろ。

原文: https://sijiaoh.com/posts/private-method-test-rethink/

Discussion