テスト容易性を設計する際に静的ユーティリティクラスを処理する方法


62

TDDを使用して開発されたほとんどの部分で、テスト可能なシステムを設計しようとしています。現在、次の問題を解決しようとしています。

さまざまな場所で、ImageIOやURLEncoder(標準Java APIの両方)などの静的ヘルパーメソッド、および大部分が静的メソッド(Apache Commonsライブラリなど)で構成されるその他のさまざまなライブラリを使用する必要があります。しかし、このような静的ヘルパークラスを使用するメソッドをテストすることは非常に困難です。

この問題を解決するためのアイデアがいくつかあります。

  1. 静的クラス(PowerMockなど)をモックできるモックフレームワークを使用します。これは最も簡単な解決策かもしれませんが、どういうわけかあきらめたように感じます。
  2. これらのすべての静的ユーティリティの周りにインスタンス化可能なラッパークラスを作成して、それらを使用するクラスに注入できるようにします。これは比較的クリーンなソリューションのように聞こえますが、これらのラッパークラスを大量に作成することになると思います。
  3. これらの静的ヘルパークラスへのすべての呼び出しをオーバーライド可能な関数に抽出し、実際にテストするクラスのサブクラスをテストします。

しかし、TDDを行う際に多くの人が直面しなければならない問題であるに違いないと私は考え続けます。したがって、この問題の解決策はすでに存在しているはずです。

これらの静的ヘルパーを使用するクラスをテスト可能に保つための最良の戦略は何ですか?


「信頼できる、および/または公式の情報源」があなたが何を意味するのか分かりませんが、@ berecursiveが彼の答えに書いたことに同意します。PowerMockには理由があり、特にラッパークラスを自分で記述したくない場合は、「あきらめる」ように感じるべきではありません。単体テスト(およびTDD)に関しては、最終メソッドと静的メソッドは苦痛です。個人的に?説明した方法2を使用します。
デコ

「信頼できるソースおよび/または公式ソース」は、質問に対する報奨金を開始するときに選択できるオプションの1つにすぎません。私が実際に意味すること:TDDの専門家によって書かれた記事の経験または参照。または、同じ問題に直面した人によるあらゆる種類の経験

回答:


34

(ここには「公式」の情報源はありません。私は恐れています-うまくテストするための仕様が存在するというわけではありません。私の意見だけで、うまくいけば役に立つでしょう。)

これらの静的メソッドが真の依存関係を表す場合、ラッパーを作成します。次のようなものに対して:

  • ImageIO
  • HTTPクライアント(またはネットワーク関連のもの)
  • ファイルシステム
  • 現在の時刻を取得する(依存関係の挿入が役立つ私のお気に入りの例)

...インターフェイスを作成することは理にかなっています。

しかし、Apache Commonsのメソッドの多くは、おそらくm笑/偽装すべきではありません。たとえば、文字列のリストを結合し、それらの間にカンマを追加する方法を取ります。これらをm笑する意味はありません -静的呼び出しに通常の動作をさせてください。通常の動作を望んでいない、または置き換える必要はありません。外部のリソースや扱いにくいものを扱っているのではなく、単なるデータです。結果は予測可能であり、あなたはそれが何になりたいことがありませんでした他のそれはとにかくあなたを与えるだろう何よりも。

私は、(HTTPのような)大量の論理的な依存関係へのエントリポイントではなく、予測可能な「純粋な」結果(base64やURLエンコーディングなど)を備えた便利なメソッドであるすべての静的呼び出しを削除したと思われます真の依存関係で正しいことをするのが実用的です。


20

これは間違いなく意見を述べた質問/回答ですが、2セントを投じる価値があると思ったからです。TDDスタイルの方法2では、間違いなく手紙に続くアプローチです。方法2の引数は、これらのクラスの1つ(ImageIO同等のライブラリなど)の実装を置き換えたい場合、そのコードを活用するクラスの信頼を維持しながらそれを行うことができるということです。

ただし、前述のように、多くの静的メソッドを使用すると、多くのラッパーコードを記述することになります。これは長期的には悪いことではないかもしれません。保守性の観点から、これには確かに議論があります。個人的に私はこのアプローチを好むでしょう。

とはいえ、PowerMockには理由があります。静的メソッドが関係する場合のテストが非常に苦痛であり、したがってPowerMockの開始が非常によく知られている問題です。PowerMockを使用する場合と、すべてのヘルパークラスをラップする場合の作業量の観点から、オプションを比較検討する必要があると思います。PowerMockを使用するのは「あきらめる」とは思いません。クラスをラップすることで、大規模プロジェクトでより柔軟に対応できると感じています。より多くのパブリックコントラクト(インターフェイス)を提供すれば、意図と実装をより明確に分離できます。


1
わからない追加の問題:ラッパーを実装するときに、ラップされているクラスのすべてのメソッドを実装するか、現在必要なメソッドだけを実装しますか?

3
アジャイルなアイデアを追うには、最も簡単に機能することを行い、不要な作業を行わないようにする必要があります。したがって、実際に必要なメソッドのみを公開する必要があります。
アサフストーン

@AssafStoneは同意しました

PowerMockには注意してください。メソッドをモックするために必要なクラス操作には、多くのオーバーヘッドが伴います。テストを広範囲に使用すると、テストが非常に遅くなります。
bcarlso

テスト/移行をDI / IoCライブラリの採用と組み合わせる場合、本当に多くのラッパーを作成する必要がありますか?

4

この問題に対処し、この問題に出くわしたすべての人への参照として、問題に取り組むことにした方法を説明します。

基本的に、#2(静的ユーティリティのラッパークラス)として概説されているパスに従います。しかし、目的の出力を生成するために必要なデータをユーティリティに提供するには複雑すぎる場合(つまり、メソッドを絶対にモックする必要がある場合)にのみ使用します。

これは、Apache Commonsのような単純なユーティリティのラッパーを記述するStringEscapeUtils必要がなく(必要な文字列を簡単に提供できるため)、静的メソッドにモックを使用しないことを意味します(記述する必要があると思われる場合)ラッパークラスを作成してから、ラッパーのインスタンスをモックします)。



1

私は大手保険会社で働いており、ソースコードは最大400MBの純粋なJavaファイルになります。TDDを考慮することなく、アプリケーション全体を開発しています。今年の1月から、個々のコンポーネントごとにjunitテストを開始しました。

私たちの部門での最良の解決策は、システムに依存する(Cで記述された)JNIメソッドでMockオブジェクトを使用することでした。そのため、すべてのOSで毎回結果を正確に推定できませんでした。サポートするすべてのOSのアプリケーションの個々のモジュールをテストするために、モッククラスと特定のJNIメソッドの実装を使用する以外のオプションはありませんでした。

しかし、それは本当に速く、これまでのところ非常にうまく機能しています。私はそれをお勧めします-http://www.easymock.org/


1

環境(Webサービスエンドポイント、DBにアクセスするdaoレイヤー、http要求パラメーターを処理するコントローラー)のためにテストが難しいオブジェクトがある場合、またはオブジェクトを分離してテストする場合、オブジェクトは相互にやり取りして目標を達成します。それらのオブジェクトをモックします。

静的メソッドのモックの必要性は悪臭です。アプリケーションをよりオブジェクト指向に設計する必要があります。また、ユニットテストユーティリティの静的メソッドはプロジェクトに大きな価値を追加しません。ラッパークラスは状況に応じて適切なアプローチですが、静的メソッドを使用するオブジェクトをテストします。


1

時々オプション4を使用します

  1. 戦略パターンを使用します。プラグ可能なインターフェイスのインスタンスに実装を委任する静的メソッドを含むユーティリティクラスを作成します。具体的な実装をプラグインする静的初期化子をコーディングします。テスト用の模擬実装をプラグインします。

このようなもの。

public class DateUtil {
    public interface ITimestampGenerator {
        long getUtcNow();
    }

    class ConcreteTimestampGenerator implements ITimestampGenerator {
        public long getUtcNow() { return System.currentTimeMillis(); }
    }

    private static ITimestampGenerator timestampGenerator;

    static {
        timestampGenerator = new ConcreteTimeStampGenerator;
    }

    public static DateTime utcNow() {
        return new DateTime(timestampGenerator.getUtcNow(), DateTimeZone.UTC);
    }

    public static void setTimestampGenerator(ITimestampGenerator t) {...}

    // plus other util routines, which may or may not use the timestamp generator 
}

このアプローチで私が気に入っているのは、ユーティリティメソッドを静的に保持することです。これは、コード全体でクラスを使用しようとするときに適切だと感じています。

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