web-dev-qa-db-ja.com

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

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

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

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

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

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

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

63
Benedikt

(ここには「公式」の情報源はありません。恐れます-うまくテストする方法の仕様があるようではありません。私の意見だけが役に立つと思います。)

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

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

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

しかし、Apache Commonsのメソッドの多くはおそらくすべきではないあざけられる/偽造される。たとえば、文字列のリストを結合するメソッドを取り、それらの間にコンマを追加します。これらのモックにはno pointがあります-静的呼び出しに通常の処理を行わせてください。通常の振る舞いを置き換える必要はありません。外部リソースや、扱いにくい何かを扱っているのではなく、単なるデータです。結果は予測可能であり、それが何であれotherになりたいとは思わないでしょう。

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

34
Jon Skeet

これは間違いなく独断的な質問/回答ですが、2セントを投入する価値があると私は考えました。TDDスタイルの方法では、方法2は間違いなくそれに続くアプローチです。メソッド2の引数は、これらのクラスのいずれかの実装(たとえば、ImageIOの同等のライブラリ)を置き換えたい場合、そのコードを活用するクラスの信頼性を維持しながら行うことができるということです。

ただし、前述のように、静的メソッドのたくさんを使用すると、大量のラッパーコードが作成されます。これは長期的には悪いことではないかもしれません。保守性に関しては、確かにこれについて議論があります。個人的に私はこのアプローチを好みます。

そうは言っても、PowerMockには理由があります。静的メソッドが含まれている場合のテストが非常に困難であることはよく知られた問題であり、PowerMockの始まりです。 PowerMockを使用する場合と比較して、ヘルパークラスをすべてラップする場合の作業量について、オプションを比較検討する必要があると思います。 PowerMockを使用することは「あきらめる」ことではないと思います。クラスをラップすることで、大規模なプロジェクトでの柔軟性が高まると思います。提供できるパブリックコントラクト(インターフェイス)が多いほど、意図と実装の分離が明確になります。

20
BeRecursive

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

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

つまり、Apache Commons StringEscapeUtilsのような単純なユーティリティのラッパーを記述する必要はなく(必要な文字列は簡単に提供できるため)、静的メソッドにモックを使用しません(考えられる場合)ラッパークラスを記述して、ラッパーのインスタンスをモックするときがきたのです)。

4
Benedikt

Groovy を使用してこれらのクラスをテストします。 Groovyは、任意のJavaプロジェクトに追加するのは簡単です。これを使用すると、静的メソッドを非常に簡単にモックアウトできます。例については、「 Groovyを使用した静的メソッドのモック 」を参照してください。

2
trevorism

オプション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);
1
bigh_29

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

私たちの部門での最良の解決策は、システムに依存する(Cで書かれた)いくつかのJNIメソッドでMockオブジェクトを使用することでした。私たちがサポートするすべてのOSのアプリケーションの個々のモジュールをそれぞれテストする目的で、モックされたクラスとJNIメソッドの特定の実装を使用する以外に選択肢はありませんでした。

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

1
BizzyDizzy

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

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