17

ClassToMockに依存するClassToTestクラスがあります。

public class ClassToMock {

  private static final String MEMBER_1 = FileReader.readMemeber1();

  protected void someMethod() {
    ...
  }
}

ClassToTestの単体テストケース。

public class ClassToTestTest {
  private ClassToMock _mock;

  @Before
  public void setUp() throws Exception {
     _mock = mock(ClassToMock.class)
  }

}

setUp()メソッドでモックが呼び出されると、FileReader.readMemeber1(); 実行されます。これを回避する方法はありますか?1つの方法は、メソッド内でMEMBER_1を初期化することだと思います。他の選択肢はありますか?

ありがとう!

4

3 に答える 3

11

あなたClassToMockは緊密に結合してFileReaderいるので、それをテスト/モックすることはできません。ツールを使用してバイトコードをハッキングする代わりに、モックを作成できます。依存関係を解消するために、いくつかの簡単なリファクタリングを行うことをお勧めします。

手順1.グローバル参照をカプセル化する

この手法は、MichaelFeathersのすばらしい本「LegacyCodeで効果的に作業する」でも紹介されています。

タイトルはほとんど自明です。グローバル変数を直接参照する代わりに、メソッド内にカプセル化します。

あなたの場合、ClassToMockこれにリファクタリングすることができます:

public class ClassToMock {
  private static final String MEMBER_1 = FileReader.readMemeber1();

  public String getMemberOne() {
    return MEMBER_1;      
  }
}

その後、Mockitoを使用して簡単にモックを作成できますgetMemberOne()

更新された古いステップ1はMockito、モックを安全に保証できませんFileReader.readMemeber1()。例外をスローすると、テストは無残に失敗します。したがって、それを回避するために別のステップを追加することをお勧めします。

ステップ1.5。セッターとレイジーゲッターを追加

問題はロードさFileReader.readMember1()れるとすぐに呼び出されるためです。ClassToMock私たちはそれを遅らせる必要があります。そこで、ゲッターをFileReader.readMember1()怠惰に呼び出し、セッターを開きます。

public class ClassToMock {
  private static String MEMBER_1 = null;

  protected String getMemberOne() {
    if (MEMBER_1 == null) {
      MEMBER_1 = FileReader.readMemeber1();
    }
    return MEMBER_1;      
  }

  public void setMemberOne(String memberOne) {
    MEMBER_1 = memberOne;
  }
}

ClassToMockこれで、がなくても偽物を作成できるはずMockitoです。ただし、これがコードの最終状態になることはありません。テストの準備ができたら、手順2に進む必要があります。

ステップ2.依存関係の注入

テストの準備ができたら、さらにリファクタリングする必要があります。今ではMEMBER_1それ自体を読む代わりに。このクラスは、MEMBER_1代わりに外界から受け取る必要があります。セッターまたはコンストラクターを使用して受け取ることができます。以下はsetterを使用するコードです。

public class ClassToMock {
  private String memberOne;
  public void setMemberOne(String memberOne) {
    this.memberOne = memberOne;
  }

  public String getMemberOne() {
    return memberOne;
  }
}

これらの2ステップのリファクタリングは非常に簡単で、手元のテストがなくても実行できます。コードがそれほど複雑でない場合は、手順2を実行するだけです。その後、簡単にテストできます。ClassToTest


更新12/8:コメントに答える

この質問の私の別の答えを参照してください。

于 2012-12-06T01:23:48.430 に答える
2

更新12/8:コメントに答える

質問:FileReaderが、すべてのクラスに存在する必要があるLoggingのような非常に基本的なものである場合はどうなりますか。私がそこで同じアプローチに従うことを提案しますか?

場合によります。

そのような大規模なリファクタリングを行う前に、考えたいことがあります。

  1. 外に移動した場合、ファイルから読み取り、それらを必要とするすべてのクラスFileReaderに結果を提供できる適切なクラスがありますか?

  2. クラスのテストを簡単にする以外に、他のメリットはありますか?

  3. 時間はありますか?

答えのいずれかが「いいえ」の場合は、そうしない方がよいでしょう。

FileReaderただし、最小限の変更で、すべてのクラス間の依存関係を解消できます。

あなたの質問とコメントから、私はあなたのシステムFileReaderがプロパティファイルからものを読み取るためのグローバルリファレンスとして使用していると仮定し、それをシステムの残りの部分に提供します。

この手法は、MichaelFeathersのすばらしい本「LegacyCodeを効果的に使用する」でも紹介されています。

手順1.FileReader静的メソッドをインスタンスに委任します。

変化する

public class FileReader {
  public static FileReader getMemberOne() {
    // codes that read file.
  }
}

public class FileReader {
  private static FileReader singleton = new FileReader();
  public static String getMemberOne() {
    return singleton.getMemberOne();
  }

  public String getMemberOne() {
    // codes that read file.
  }
}

これを行うことにより、静的メソッドはFileReader現在、getMemberOne()

ステップ2.からインターフェイスを抽出しますFileReader

public interface AppProperties {
  String getMemberOne();
}

public class FileReader implements AppProperties {
  private static AppProperties singleton = new FileReader();
  public static String getMemberOne() {
    return singleton.getMemberOne();
  }

  @Override
  public String getMemberOne() {
    // codes that read file.
  }
}

すべてのメソッドをに抽出し、AppProperties静的インスタンスをに抽出しFileReaderますAppProperties

ステップ3.静的セッター

public class FileReader implements AppProperties {
  private static AppProperties singleton = new FileReader();

  public static void setAppProperties(AppProperties prop) {
    singleton = prop;
  }

  ...
  ...
}

FileReaderでシームを開きました。これを行うことで、基になるインスタンスの変更を設定でき、FileReader気付くことはありません。

ステップ4.クリーンアップ

FileReader、2つの責任があります。1つはファイルの読み取りと結果の提供であり、もう1つはシステムのグローバル参照の提供です。

それらを分離して、適切な名前を付けることができます。結果は次のとおりです。

// This is the original FileReader, 
// now is a AppProperties subclass which read properties from file.
public FileAppProperties implements AppProperties {
  // implementation.
}

// This is the class that provide static methods.
public class GlobalAppProperties {

  private static AppProperties singleton = new FileAppProperties();

  public static void setAppProperties(AppProperties prop) {
    singleton = prop;
  }

  public static String getMemberOne() {
    return singleton.getMemberOne();
  }
  ...
  ...
}

終わり。

このリファクタリングの後、テストしたいときはいつでも。あなたはモックAppPropertiesを設定することができますGlobalAppProperties

このリファクタリングは、多くのクラスで同じグローバル依存関係を解消するだけの場合に適していると思います。

于 2012-12-08T05:02:18.987 に答える