Проверка штормовых болтов и носиков

Это общий вопрос, касающийся болтов и носиков модульного тестирования в топологии Storm, написанной на Java.

Каковы рекомендуемые методы и правила для модульного тестирования (JUnit?) Bolts и Spouts?

Например, я мог бы написать тест JUnit для Bolt, но без полного понимания структуры (например, жизненного цикла 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, например - его объявленные выходы)?

Спасибо


person Jack    schedule 14.05.2013    source источник
comment
Вы когда-нибудь выбирали подход?   -  person Chris Gerken    schedule 04.06.2013
comment
Что ж, я прочитал ответы ниже. Кажется, там есть какие-то общие рекомендации, но ничего не высеченного в камне. Я собираюсь оставить вопрос открытым на некоторое время, чтобы посмотреть, есть ли у кого-нибудь еще какие-нибудь мысли, а затем закрою его. Мне нравятся оба подхода к использованию API тестирования (TestingApiDemo.java), а также ваш ответ на насмешку над зависимостями, @ChrisGerken.   -  person Jack    schedule 05.06.2013


Ответы (4)


Наш подход заключается в использовании конструктора-инъекции сериализуемой фабрики в носик / болт. Затем носик / болт консультируется с заводом относительно метода открытия / подготовки. Единственная обязанность фабрики - инкапсулировать получение зависимостей носика / болта сериализуемым способом. Такой дизайн позволяет нашим модульным тестам внедрять фальшивые / тестовые / имитирующие фабрики, которые при обращении к ним возвращают фиктивные сервисы. Таким образом, мы можем узко провести модульное тестирование носика / болтов, используя макеты, например. Мокито.

Ниже приведен общий пример болта и его проверка. Я пропустил реализацию фабрики 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));
  }
}
person Carl G    schedule 13.11.2014
comment
Я знаю, что это старый пост, так что простите меня, если вы ушли от этого проекта :) Мне интересно, есть ли тест для verify (notifier) ​​.notifyUser (userId); прошедший. Я обнаружил, что сериализация и десериализация, которые Storm выполняет на фабрике, вызывают создание нового имитационного уведомителя. Таким образом, исходный фиктивный уведомитель не получает никаких взаимодействий. Это было для вас? - person ilana917; 11.05.2017
comment
@ ilana917 Storm и mocks не должны взаимодействовать. Цель этого шаблона - написать код таким образом, чтобы вы могли тестировать код отдельно от среды выполнения Storm. В тесте сериализации не должно происходить. Между строками new NotifyUserBolt и bolt.prepare в тесте нет сериализации. Во время выполнения Storm Storm будет сериализовать Bolt. - person Carl G; 12.05.2017
comment
Спасибо @ carl-g, я иду в этом направлении. Я нахожусь в разгаре большого рефакторинга и изначально планировал запустить шторм от начала до конца как своего рода интеграционный тест с использованием имитаций для внешних подключений, но модульное тестирование наша логика имеет больше смысла. - person ilana917; 14.05.2017

Начиная с версии 0.8.1 средства модульного тестирования Storm доступны через Java:

Пример использования этого API можно найти здесь:

person asmaier    schedule 09.10.2013
comment
Действительно хороший API, хотя немного больше документации помогло бы в его понимании. - person Barry NL; 25.11.2014
comment
Вот сообщение в блоге, которое может быть полезно: pixelmachine.org/ 2011/12/17 / Testing-Storm-Topologies.html В нем объясняется API clojure для тестирования шторма, но это в основном то же самое, что и Java API. - person asmaier; 04.12.2015
comment
Однако это не модульный тест. Это интеграционный тест, поскольку он запускает кластер в памяти для выполнения болта. Модульный тест - это тот, который проверяет наименьший возможный элемент, который будет отдельными методами болта. - person user167019; 22.04.2019

Один из принятых нами подходов состоит в том, чтобы переместить большую часть логики приложения из болтов и сливов в объекты, которые мы используем для выполнения тяжелой работы, создавая их экземпляры и используя их через минимальные интерфейсы. Затем мы проводим модульное тестирование этих объектов и интеграционное тестирование, хотя это оставляет пробел.

person G Gordon Worley III    schedule 14.05.2013
comment
Хотя то, что вы говорите, верно и является хорошей идеей, это не соответствует интересам OP в поиске таких вещей, как проблемы сериализации, перед выполнением производственного развертывания. - person Steven Magana-Zook; 06.08.2014
comment
Благодарю за ваш ответ! Мне любопытно, что вы имели в виду под словом «оставить зазор»? - person taylorcressy; 10.03.2015
comment
разрыв находится на границе между штормом и нашими объектами. эта часть не тестируется тщательно, потому что она появляется только в интеграционных тестах, что дорого делать исчерпывающим, поэтому есть некоторый соединительный код, который недостаточно хорошо освещен - person G Gordon Worley III; 10.03.2015

Оказывается, довольно легко имитировать объекты шторма, такие как OutputDeclarer, Tuple и OutputFieldsDeclarer. Из них только OutputDeclarer когда-либо видит какие-либо побочные эффекты, поэтому, например, закодируйте фиктивный класс OutputDeclarer, чтобы иметь возможность отвечать на любые испускаемые кортежи и привязки. Затем ваш тестовый класс может использовать экземпляры этих фиктивных классов, чтобы легко настроить экземпляр болта / носика, вызвать его и проверить ожидаемые побочные эффекты.

person Chris Gerken    schedule 04.06.2013