这是关于用Java编写的风暴拓扑中的单元测试螺栓和喷口的一般性问题。
单位测试(JUnit?) Bolts 和 Spouts 的推荐做法和指南是什么?
例如,我可以为Bolt
编写一个JUnit测试,但是如果没有完全理解框架(如Bolt
的生命周期)和序列化含义,很容易犯错误的构造函数 - 基于非可序列化成员变量的创建。在JUnit中,此测试将通过,但在拓扑中,它将无法工作。我完全可以想象有许多测试点需要考虑(比如序列化和生命周期的这个例子)。
因此,如果您使用基于JUnit的单元测试,建议您运行一个小型模拟拓扑(LocalMode
?)并测试Bolt
(或Spout
的隐含合约)在那个拓扑下?或者,使用JUnit是否可以,但其含义是我们必须仔细模拟Bolt的生命周期(创建它,调用prepare()
,模拟Config
等)?在这种情况下,要考虑的被测类(Bolt / Spout)的一般测试点是什么?
在创建适当的单元测试方面,其他开发人员做了什么?
我注意到有一个拓扑测试API(参见:https://github.com/xumingming/storm-lib/blob/master/src/jvm/storm/TestingApiDemo.java)。是否更好地使用某些API,并为每个Bolt
& Spout
(并验证Bolt必须提供的隐含合同,例如 - 它是声明的输出)?
由于
答案 0 :(得分:14)
自0.8.1版以来,Storm的单元测试设施已通过Java公开:
有关如何使用此API的示例,请查看此处:
答案 1 :(得分:11)
我们采取的一种方法是将大部分应用程序逻辑从螺栓和喷口中移出,并通过实例化并通过最小接口使用它们来进行繁重的操作。然后我们对这些对象和集成测试进行单元测试,尽管这确实留下了空白。
答案 2 :(得分:10)
我们的方法是使用可序列化工厂的构造函数注入到spout / bolt中。然后,喷嘴/螺栓以其开放/准备方法咨询工厂。工厂的唯一责任是以可序列化的方式封装获取喷口/螺栓的依赖关系。这种设计允许我们的单元测试注入假/测试/模拟工厂,当被咨询时,返回模拟服务。通过这种方式,我们可以使用模具对管口/螺栓进行狭窄的单元测试,例如的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));
}
}
答案 3 :(得分:1)
模拟StormDeclarer,Tuple和OutputFieldsDeclarer等风暴对象相当容易。其中,只有OutputDeclarer看到任何副作用,因此编码OutputDeclarer模拟类能够回答任何元组和发射的锚点,例如。然后,您的测试类可以使用这些模拟类的实例来轻松配置bolt / spout实例,调用它并验证预期的副作用。