web-dev-qa-db-ja.com

静的メソッドのモック

最近、テスト駆動開発(TDD)コースから戻ったとき、私は次のことを考えました。

Mockitoを使用して単体テストを作成しているときに、静的メソッドのモックの問題に直面しました。静的メソッドのモックを可能にするPowerMockを使用する1人の開発者から提案された後、この問題にどのように対処すべきかについての大きな議論に入りました。

ただし、私の経験では、静的メソッドは実際にはユーティリティタイプのメソッドとしてのみ使用する必要があります。 Java is Math.random()の古典的な例。

したがって、静的メソッドは、クラスインスタンスとの相互作用に関係なく、定義された1つのアクションのみを実行する必要があります。これが当てはまらず、クラスインスタンスの相互作用が必要な場合、メソッドは静的であってはならず、設計を再考する必要があります。

だから私の質問は、これを念頭に置いて、静的メソッドをモックする必要があるのでしょうか?それらが常に単純なアクションを実行している場合、確かに実際のコードで行うようにそれらを呼び出す必要があります。

4
user115440

だから私の質問は、これを念頭に置いて、静的メソッドをモックする必要があるのでしょうか?それらが常に単純なアクションを実行している場合、確かに実際のコードで行うようにそれらを呼び出す必要があります。

はい、コードが適切に記述されている場合。時々そうではなく、それでもテストする必要があります。リファクタリングは非常に時間がかかる可能性があるため(静的は通常、高結合やその他のno-nosに関連しています)、これは一種の回避策です。

リファクタリングは実行不可能であるだけでなく、不可能である可能性があります。静的メソッドは手の届かない可能性があります-コードがサードパーティのライブラリから静的メソッドを呼び出す必要があり、ステートレスではなく、実行に長い時間がかかり、特別な要件(データベース、Webアクセス)がある場合...コードをテストするには、このメソッドをモックする必要があります。

2
Konrad Morawski

一般的に言えば、@ KonradMorawskiの答えは適切で完全です。ただし、staticメソッドをモックすることが重要であると考えるシナリオが1つあることを付け加えておきます。それは、staticファクトリメソッドです。

個人的には、私のコードではstaticファクトリメソッドを使用することはほとんどありません。作成されたオブジェクトがステートレスで、ファクトリーがステートレスで、作成されたオブジェクトに依存関係がない場合にのみ使用します。それでも、ほとんどの場合はそうでもありません。それ以外の場合は、ファクトリオブジェクトのインスタンスを使用します。このインスタンスをモックできます。

提供されているstaticファクトリメソッドの使用に関する問題は、作成されたオブジェクトがmocksまたはspysにならないことです。つまり、verifyこれらのオブジェクトへのアクセスを事前に制御することはありません。たとえば、次のような数学アプリケーションについて考えてみます。

_public abstract class ComplexNumber {
    private double realPart;
    private double imaginaryPart;
    ComplexNumber(double realPart, double imaginaryPart) {
        this.realPart = realPart;
        this.imaginaryPart = imaginaryPart;
    }
    public static ComplexNumber create(double realPart, double imaginaryPart) {
        return new ComplexNumber(realPart, imaginaryPart);
    }
}

public class RootCalculator {
    // Whatever is appropriate
    public List<ComplexNumber> roots(double... coefficients) {
        // somewhere in the code
        ComplexNumber number = new ComplexNumber(something, somethingElse);
        // blah blah
    }
}

public class RootCalculatorTest {
     @Test
     public testCalculator() {
         RootCalculator calc = new RootCalculator();
         List<ComplexNumber> roots = calc.roots(1, 0, 1); // equivalent to x^2 + 1 = 0
         for(ComplexNumber number : roots) {
             // verify(...);
         }
     }
}
_

返された複素数に対してverifyを呼び出すことはできず、ファクトリメソッド自体が正しい回数呼び出されたことをverifyすることもできないことに注意してください。 PowerMockを使用すると、両方を実行できます。ここでPowerMockを使用する方が、new ComplexNumber()を呼び出すよりも優れていることに注意してください。

もちろん、それを行うための最良の方法は、ファクトリーのインスタンスをインジェクトすることです。これは、モック化され、PowerMockを回避して、通常のテスト動作をすべて実行できるようにします。

2
durron597

静的メソッドのモックは悪い考えです。レガシーコードをテストしていて、なんらかの理由でリファクタリングできない場合にのみ使用します。しかし、通常の開発TDDサイクルでは、悪臭がします。

私は常にIan Cooperの「保持したいテストのこと」の1つのフレーズを覚えています。テストで静的なものをモックしているときに、機能ではなく保持したいという静的なメソッド呼び出しでこのデザインを保持したいのです。 。これを保存してもよろしいですか?

1
AlfredoCasado

私はdurron597に同意しますが、1つの例外があります。私がインジェクトしていたモックオブジェクトがテスト中のコード内で変更する必要がある場合があるため、モックオブジェクトではなくモックファクトリをインジェクトする必要があるコードの一部を確認しているときに、この質問を発見しました。これにより、コードの別のセクションを分析することになり、静的なファクトリメソッドを使用する必要があると感じる別のケースがあることがわかりました。 durron597が上で言うように:

「作成されたオブジェクトがステートレスで、ファクトリがステートレスで、作成されたオブジェクトに依存関係がない場合」

しかし、私が問題を抱えている部分は、「作成されたオブジェクトには依存関係がない」です。それがオブジェクトをモックするポイントではありませんか?依存関係と、「実際の」実装から返される可能性のある値を削除または置換したい。

私の場合、静的ファクトリー・メソッドがこのモックを返すようにしたいので、保護されたコンストラクターを介して注入していたインスタンスが1つだけ必要だったときと同じです。したがって、PowerMockを使用するか、言語構造の完全に合法的でエレガントな使用を放棄して、本質的にstaticのメソッドを備えたインスタンスを作成する必要があると考えるのは悲しいことです。そして、はい、前に触れたように、別の場所に別の工場が新たに必要であることを発見したとき、まさに後者を実行していることを発見しました。そのため、Mockitoがstaticをモックできないことがわかったときに、静的なファクトリメソッドを使用するように最初にこのケースを変更し始めました。

さらに、単体テストを実行するためだけに「インスタンスを作成する」アプローチへの洞窟探検は、知る必要のない何かについての知識を持つレイヤーがあることを意味します。私のファクトリーがこのレベル以上のパラメーターまたは初期化を必要としないほど単純である場合、このアプローチが必要とするカップリングを回避し、必要なレベルでのみ静的ファクトリーメソッドを使用したいです。この場合、上の層は工場が含まれているかどうかについてあまり気にする必要はありません!

つまり、要約すると、はいstatic[〜#〜] can [〜#〜]は匂いの指標になりますが、すべての言語構造と同様に、適切な時間と場所があります彼ら全員にとって、それが彼らがそこにいる理由ですよね? :)

0
Thomas Doman

私の経験では、次の場合に静的メソッドを合理的にモックできます。 *静的メソッドは、変更できないサードパーティライブラリからのものであり、テストの実行を遅くする可能性のある複雑な依存関係が必要になる場合があります。 *静的メソッドとの相互作用に本当に関心がある場合は、結果自体よりも重要です。

たとえば、非常に時間のかかる静的メソッドの周りのキャッシュとして機能するクラスを開発しているとします。あなたがテストしたいのはおそらく

  • キャッシュが初めて呼び出されたときに、同じパラメーターが高価な静的メソッドに提供されます。
  • すでに使用されている一連のパラメータを使用して2回目にキャッシュが呼び出されると、キャッシュヒットが発生し、静的メソッドはまったく呼び出されません。

これは私の考えを理解してもらうための基本的な例にすぎません。

可能であれば、テストで静的メソッドをモックする必要がないように通常努めていることを認めなければなりません。

0
danidemi