サブクラスまたは抽象親クラスを単体テストする必要がありますか?


12

Effective JavaのItem 18にあるように、スケルトン実装を持っています(詳細な議論はこちら)。これは抽象クラスであり、2つのパブリックメソッドmethodA()およびmethodB()を提供し、サブクラスメソッドを呼び出して、抽象化された方法では定義できない「ギャップを埋める」。

最初に具体的なクラスを作成し、そのための単体テストを作成して開発しました。2番目のクラスが来たとき、共通の動作を抽出し、2番目のクラスに「欠落したギャップ」を実装することができました(もちろん、2番目のサブクラスに対して単体テストが作成されました)。

時間が経ち、今では4つのサブクラスがあり、それぞれが具体的な実装に非常に固有の3つの短いプロテクトメソッドを実装していますが、骨格実装はすべての一般的な作業を行います。

私の問題は、新しい実装を作成するときに、もう一度テストを書くことです。

  • サブクラスは特定の必須メソッドを呼び出しますか?
  • 特定のメソッドに特定の注釈がありますか?
  • メソッドAから期待される結果が得られますか?
  • メソッドBから期待される結果が得られますか?

このアプローチの利点を見ながら:

  • テストを通じてサブクラスの要件を文書化します
  • 問題のあるリファクタリングの場合、高速で失敗する可能性があります
  • サブクラス全体とパブリックAPIを使用してテストします(「methodA()は機能しますか?」ではなく、「このprotectedメソッドは機能しますか?」)

私が持っている問題は、新しいサブクラスのテストは基本的に簡単です:-テストはすべてのサブクラスですべて同じであり、メソッドの戻り値の型を変更するだけです(スケルトン実装はジェネリックです)。テストのアサート部分を確認してください。-通常、サブクラスにはそれらに固有のテストはありません

テストがサブクラス自体の結果に焦点を当て、リファクタリングから保護する方法が好きですが、テストの実装が単なる「手動作業」になったという事実は、私が何か間違ったことをしていると思うようにします。

クラス階層をテストするときにこの問題は一般的ですか?どうすればそれを回避できますか?

ps:スケルトンクラスのテストについて考えましたが、抽象クラスのモックを作成してテストできるようにするのも奇妙に思えます。そして、サブクラスが予期していない動作を変更する抽象クラスのリファクタリングは、それほど速くは気付かれないと思います。私の勇気は、サブクラスを「全体として」テストすることがここで好ましいアプローチであることを教えてくれますが、間違っていると教えてください:)

ps2:私はグーグルで調べて、このテーマに関する多くの質問を見つけました。そのうちの一つがこれ一つであり偉大な答えナイジェルソーンからの、私の場合は彼の「ナンバー1」になります それは素晴らしいことですが、この時点ではリファクタリングできないので、この問題に耐えなければなりません。リファクタリングできれば、各サブクラスを戦略として使用できます。それは問題ありませんが、「メインクラス」と戦略をテストしますが、メインクラスと戦略の統合を壊すリファクタリングは見当たりません。

ps3:抽象クラスをテストすることは「受け入れられる」という回答をいくつか見つけました。これは受け入れられることに同意しますが、この場合にどちらが好ましいアプローチであるかを知りたいと思います(私はまだ単体テストから始めています)


2
抽象テストクラスを記述し、具体的なテストにそれを継承させ、ビジネスコードの構造をミラーリングします。テストでは、ユニットの実装ではなく、動作をテストすることになっていますが、それは、システムに関するインサイダーの知識を使用して、その目標をより効率的に達成できないという意味ではありません。
キリアンフォス

テストで継承を使用すべきでないことをどこかで読みました。注意深く読むためのソースを探しています
-JSBach

4
@KilianFothこのアドバイスは確かですか?これは、脆弱な単体テストのレシピのように聞こえますが、設計の変更に非常に敏感であるため、あまりメンテナンスできません。
コンラッドモラウスキー

回答:


5

抽象基本クラスのテストをどのように記述するかわかりません。インスタンス化できないためテストするインスタンスを作成できません。それをテストする目的のためだけに、「ダミー」の具象サブクラスを作成することができます-どのくらいの使用量になるかはわかりません。YMMV。

新しい実装(クラス)を作成するたびに、その新しい実装を実行するテストを作成する必要があります。機能の一部(「ギャップ」)はサブクラス内に実装されいるため、これは絶対に必要です。継承された各メソッドの出力は、新しい実装ごとに異なる場合があります(おそらくそうである必要があります)。


はい、それらは異なります(タイプとコンテンツは異なります)。技術的に言えば、このクラスをモックするか、空の実装でテストするためだけの簡単な実装を行うことができます。私はこのアプローチが好きではありません。しかし、新しい実装でテストに合格することを「必要としない」という事実は、私を奇妙に感じさせ、このテストで私がどれだけ自信があるかについても疑問にさせます(もちろん、意図的に基本クラスを変更すると、テストは失敗します。これは私が信頼できる証拠です)
-JSBach

4

通常、基本クラスを作成してインターフェイスを実施します。以下の詳細ではなく、そのインターフェイスをテストする必要があります。したがって、各クラスを個別にインスタンス化するテストを作成する必要がありますが、基本クラスのメソッドを介してのみテストします。

この方法の利点は、インターフェイスをテストしたため、インターフェイスの下でリファクタリングできるようになったことです。子クラスのコードは親にある必要があります。逆も同様です。これで、テストにより、コードを移動したときに動作を中断しなかったことを証明できます。

一般に、コードの使用方法をテストする必要があります。これは、プライベートメソッドのテストを回避することを意味し、テスト対象のユニットが当初考えていたよりも少し大きくなる可能性があります。おそらく、単一のクラスではなくクラスのグループです。目標は、特定のスコープでリファクタリングするときにテストを変更する必要がほとんどないように、変更を分離することです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.