48

これは、Java で記述された Storm トポロジーでのボルトとスパウトの単体テストに関する一般的な質問です。

単体テスト (JUnit?)ボルトスパウトの推奨プラクティスとガイドラインは何ですか?

たとえば、 a の JUnit テストを作成することはできますがBolt、フレームワーク ( a のライフサイクルなど) とシリアライゼーションの影響を完全に理解していないBoltと、シリアライズできないメンバー変数をコンストラクター ベースで作成するという間違いを犯しがちです。JUnit ではこのテストに合格しますが、トポロジでは機能しません。考慮する必要があるテスト ポイントがたくさんあることは十分に想像できます (シリアル化とライフサイクルを使用したこの例など)。

したがって、JUnit ベースの単体テストを使用する場合は、小さなモック トポロジ ( ?) を実行し、そのトポロジの下で(または)LocalModeの暗黙のコントラクトをテストすることをお勧めしますか? それとも、JUnit を使用しても問題ありませんが、Bolt のライフサイクル (作成、呼び出し、モック化など) を慎重にシミュレートする必要があることを意味しますか? この場合、テスト対象のクラス (Bolt/Spout) で考慮すべき一般的なテスト ポイントは何ですか?BoltSpoutprepare()Config

適切な単体テストの作成に関して、他の開発者は何をしましたか?

トポロジ テスト API があることに気付きました (参照: https://github.com/xumingming/storm-lib/blob/master/src/jvm/storm/TestingApiDemo.java )。Boltその API の一部を使用して、個々の&ごとに「テスト トポロジ」を立ち上げた方がよいでしょうかSpout(そして、Bolt が提供しなければならない暗黙のコントラクトを検証します。たとえば、宣言された出力です)。

ありがとう

4

4 に答える 4

14

バージョン 0.8.1 以降、Storm の単体テスト機能は Java 経由で公開されています。

この API の使用方法の例については、こちらをご覧ください。

于 2013-10-09T13:07:32.847 に答える
11

私たちのアプローチは、シリアル化可能なファクトリのコンストラクター注入をスパウト/ボルトに使用することです。スパウト/ボルトは、オープン/準備方法で工場に相談します。ファクトリの唯一の責任は、シリアル化可能な方法でスパウト/ボルトの依存関係を取得することをカプセル化することです。この設計により、単体テストでフェイク/テスト/モック ファクトリを注入できるようになり、参照するとモック サービスが返されます。このようにして、Mockito などのモックを使用して、スパウト/ボルトを狭い範囲で単体テストできます。

以下は、ボルトとそのテストの一般的な例です。UserNotificationFactoryアプリケーションに依存するため、ファクトリの実装は省略しました。サービスロケーターを使用して、サービス、シリアル化された構成、HDFS アクセス可能な構成、または実際にはあらゆる方法で正しいサービスを取得することができます (ファクトリが Serde サイクルの後にそれを実行できる場合)。そのクラスのシリアル化をカバーする必要があります。

ボルト

public class NotifyUserBolt extends BaseBasicBolt {
  public static final String NAME = "NotifyUser";
  private static final String USER_ID_FIELD_NAME = "userId";

  private final UserNotifierFactory factory;
  transient private UserNotifier notifier;

  public NotifyUserBolt(UserNotifierFactory factory) {
    checkNotNull(factory);

    this.factory = factory;
  }

  @Override
  public void prepare(Map stormConf, TopologyContext context) {
    notifier = factory.createUserNotifier();
  }

  @Override
  public void execute(Tuple input, BasicOutputCollector collector) {
    // This check ensures that the time-dependency imposed by Storm has been observed
    checkState(notifier != null, "Unable to execute because user notifier is unavailable.  Was this bolt successfully prepared?");

    long userId = input.getLongByField(PreviousBolt.USER_ID_FIELD_NAME);

    notifier.notifyUser(userId);

    collector.emit(new Values(userId));
  }

  @Override
  public void declareOutputFields(OutputFieldsDeclarer declarer) {
    declarer.declare(new Fields(USER_ID_FIELD_NAME));
  }
}

テスト

public class NotifyUserBoltTest {

  private NotifyUserBolt bolt;

  @Mock
  private TopologyContext topologyContext;

  @Mock
  private UserNotifier notifier;

  // This test implementation allows us to get the mock to the unit-under-test.
  private class TestFactory implements UserNotifierFactory {

    private final UserNotifier notifier;

    private TestFactory(UserNotifier notifier) {
      this.notifier = notifier;
    }

    @Override
    public UserNotifier createUserNotifier() {
      return notifier;
    }
  }

  @Before
  public void before() {
    MockitoAnnotations.initMocks(this);

    // The factory will return our mock `notifier`
    bolt = new NotifyUserBolt(new TestFactory(notifier));
    // Now the bolt is holding on to our mock and is under our control!
    bolt.prepare(new Config(), topologyContext);
  }

  @Test
  public void testExecute() {
    long userId = 24;
    Tuple tuple = mock(Tuple.class);
    when(tuple.getLongByField(PreviousBolt.USER_ID_FIELD_NAME)).thenReturn(userId);
    BasicOutputCollector collector = mock(BasicOutputCollector.class);

    bolt.execute(tuple, collector);

    // Here we just verify a call on `notifier`, but we could have stubbed out behavior befor
    //  the call to execute, too.
    verify(notifier).notifyUser(userId);
    verify(collector).emit(new Values(userId));
  }
}
于 2014-11-13T23:42:19.737 に答える
1

OutputDeclarer、Tuple、OutputFieldsDeclarer などのストーム オブジェクトをモックするのはかなり簡単であることがわかりました。それらのうち、OutputDeclarer のみが副作用を確認するため、たとえば、出力されたタプルとアンカーに応答できるように OutputDeclarer モック クラスをコーディングします。その後、テスト クラスはこれらのモック クラスのインスタンスを使用して、簡単にボルト/スパウト インスタンスを構成し、それを呼び出して、予想される副作用を検証できます。

于 2013-06-04T15:34:09.343 に答える