3

シングルトン クラスの単体テストを作成したいのですが、このクラスは UI コンポーネントに依存しています。このクラスにはPageManager、ページ履歴を前後に移動する機能があります。単体テストでは、この履歴機能をテストするのが好きですが、このテストには必要ないため、UI のものを初期化したくありません。

私はJMockitが初めてで、これを試してみましたが、成功しませんでした:モックされる元のクラスは次のとおりです:

public final class PageManager {
  private static final PageManager INSTANCE = new PageManager();

  private final Set<Page> pages = new HashSet<>();
  private Page currentPage;
  private boolean initialized = false;

  private PageManager() {
    // Do some UI stuff
  }

  public static PageManager getInstance() {
    return INSTANCE;
  }

  public void addPage(final Page page) {
    pages.add(page);
  }

  public void initialize() {
    // Do some UI stuff
    initialized = true;
  }

  public Page getPage() { return currentPage; }
  public void setPage(final Page page) { ... }

  public void goBack() { ... };
  public void goForward() { ... };
  public boolean canGoBack() { ... };
  public boolean canGoForward() { ... };

  private void activatePage(final Page page) {
    // Do some UI stuff
    this.currentPage = page;
  }

  private void deactivatePage(final Page page) {
    // Do some UI stuff
  }
}

これは嘲笑されたバージョンです:

public final class MockedPageManager extends MockUp<PageManager> {
  PageManager instance;

  @Mock
  void $init(final Invocation invocation) {
    instance = invocation.getInvokedInstance();
  }

  @Mock
  void initialize() {
    // Don't do UI stuff
    Deencapsulation.setField(instance, "initialized", true);
  }

  @Mock
  void activatePage(Page page) {
    Deencapsulation.setField(instance, "currentPage", page);
    page.activate();
  }

  @Mock
  void deactivatePage(Page page) {
  }
}

そして小さなテスト:

@Test
public void testGoBack() {
  new MockedPageManager();

  final Page p1 = new Page() { @Override public String getTitle() { return "p1"; } };
  final Page p2 = new Page() { @Override public String getTitle() { return "p2"; } };

  final PageManager pm = PageManager.getInstance();
  pm.addPage(p1);
  pm.addPage(p2);
  pm.initialize();

  pm.setPage(p1)
  assertEquals(p1, pm.getCurrentPage());
  pm.setPage(p2);
  assertEquals(p2, pm.getCurrentPage())
  assertTrue(pm.canGoBack());
  pm.goBack();
  assertEquals(p1, pm.getCurrentPage());
}

このテストでは、$initJMockit によってメソッドが正しく呼び出されます。問題は、が呼び出されたNullPointerExceptionsときにテストで an がスローされることです。スタックトレースは、フィールドがpm.addPage(p1)であるため、元のクラスで NPE が発生することを示しています。PageManagerSet pagesnull

私の質問は: このシングルトン クラスは正しくモックされていますか? メソッドはコンストラクターのみをオーバーライドしますか、$initそれとも Java インスタンス初期化子もオーバーライドしますか。つまり、Set pages = new HashSet<>();

4

1 に答える 1

1

ここで述べたように、インスタンスの初期化ブロックまたはステートメントは、(コンパイラーによって) 各コンストラクターにコピーされます。JMockit はリフレクション/バイト コード操作を使用してクラスのコンストラクターをモックし、すべての初期化コードを効果的に回避していると思われます。したがって、初期化子は実行されず、set 変数は null のままです。本当にこれを機能させる必要がある場合は、モックで適切に初期化してみてください。いっそのこと、クラスをリファクタリングして、テストで使用できるようにします (たとえば、注入された依存関係をテストするためのパッケージのプライベート コンストラクターを追加するか、ページ履歴機能を独自のクラスに移動します)。

于 2013-09-18T10:50:41.797 に答える