これは、Javaで記述されたStormトポロジのボルトとスパウトの単体テストに関する一般的な質問です。
単体テストの推奨プラクティスとガイドラインは何ですか(JUnit?)BoltsandSpouts?
たとえば、Bolt
のJUnitテストを書くことができますが、フレームワーク(Bolt
のライフサイクルなど)とシリアライゼーションの意味を完全に理解することなく、コンストラクターベースのミスを簡単に犯すことができます。シリアル化できないメンバー変数の作成。 JUnitでは、このテストは合格しますが、トポロジでは機能しません。検討する必要のあるテストポイントが多数あることを完全に想像します(シリアル化とライフサイクルを使用したこの例など)。
したがって、JUnitベースの単体テストを使用する場合、小さな模擬トポロジ(LocalMode
?)を実行し、Bolt
(またはSpout
の暗黙のコントラクトをテストすることをお勧めします)そのトポロジーの下で?または、JUnitを使用しても構いませんが、Boltのライフサイクルをシミュレートする必要があるという意味です(作成、prepare()
の呼び出し、Config
のモックなど)。この場合、テスト対象のクラス(ボルト/スパウト)の一般的なテストポイントは何ですか?
適切な単体テストの作成に関して、他の開発者は何をしましたか?
トポロジテストAPIがあることに気付きました(参照: https://github.com/xumingming/storm-lib/blob/master/src/jvm/storm/TestingApiDemo.Java )。そのAPIの一部を使用し、個々のBolt
&Spout
に対して「テストトポロジ」を立てること(およびBoltが提供しなければならない暗黙のコントラクトを検証すること、たとえば-宣言された出力)?
ありがとう
私たちのアプローチは、シリアル化可能なファクトリーのコンストラクター注入をスパウト/ボルトに使用することです。その後、スパウト/ボルトは、オープン/準備方法で工場に相談します。工場の唯一の責任は、シリアル化可能な方法でスパウト/ボルトの依存関係の取得をカプセル化することです。この設計により、単体テストで偽/テスト/モックファクトリを挿入し、相談するとモックサービスを返すことができます。このようにして、モックを使用してスパウト/ボルトの狭いユニットテストを実行できます。モッキート。
以下は、ボルトの一般的な例とそのテストです。ファクトリUserNotificationFactory
の実装は、アプリケーションに依存するため省略しました。工場がserdeサイクルの後にそれを行うことができる限り、サービスロケーターを使用して、サービス、シリアル化された構成、HDFSアクセス可能な構成、または実際にあらゆる方法を取得して正しいサービスを取得できます。そのクラスのシリアル化について説明する必要があります。
ボルト
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));
}
}
バージョン0.8.1以降、Stormのユニットテスト機能はJava経由で公開されています。
このAPIの使用例については、こちらをご覧ください。
私たちが取ったアプローチの1つは、ほとんどのアプリケーションロジックをボルトとスパウトから、最小限のインターフェイスを介してインスタンス化して使用することにより、重いリフティングを行うオブジェクトに移動することです。次に、これらのオブジェクトの単体テストと統合テストを行いますが、これにはギャップがあります。
OutputDeclarer、Tuple、OutputFieldsDeclarerのようなストームオブジェクトを簡単にモックすることができます。それらのうち、OutputDeclarerのみが副作用を目にするため、OutputDeclarerモッククラスをコーディングして、たとえば、出力されたタプルやアンカーに応答できるようにします。テストクラスは、これらのモッククラスのインスタンスを使用して、ボルト/スパウトインスタンスを簡単に構成し、呼び出して、予想される副作用を検証できます。