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