Статьи

Тестирование программ Hadoop с MRUnit

В этом посте мы немного отклонимся от реализации шаблонов, найденных в  Data-Intensive Processing с MapReduce,  для обсуждения чего-то столь же важного, тестирования. Отчасти меня вдохновила презентация  Тома Уилера,  которую я посетил на Всемирной конференции Strata / Hadoop 2012 года в Нью-Йорке. При работе с большими наборами данных модульное тестирование может быть не первым, что приходит на ум. Однако, если учесть тот факт, что независимо от того, насколько велик ваш кластер или сколько у вас данных, один и тот же код передается на все узлы для выполнения задания MapReduce, средства отображения и редукторы Hadoop очень хорошо подходят для модульного тестирования. , Но что не так просто в модульном тестировании Hadoop, так это сама структура. К счастью, есть библиотека, которая делает тестирование Hadoop довольно простым — Мрунит . MRUnit основан на JUnit и позволяет проводить модульное тестирование преобразователей, преобразователей и некоторых ограниченных интеграционных тестов взаимодействия преобразователя и преобразователя вместе с объединителями, настраиваемыми счетчиками и разделителями. На момент написания этой статьи мы использовали последнюю версию MRUnit, 0.9.0. Весь тестируемый код взят из  предыдущего поста  о вычислении средних значений с использованием локальной агрегации.

Настроить

Для начала загрузите MRUnit  отсюда . После того, как вы извлекли файл tar, перейдите в каталог mrunit-0.9.0-инкубационный / lib. Там вы должны увидеть следующее:

  1. mrunit-0.9.0-инкубирования-hadoop1.jar
  2. mrunit-0.9.0-инкубирования-hadoop2.jar

Как я уверен, можно догадаться, что mrunit-0.9.0 -ugating-hadoop1.jar предназначен для MapReduce версии 1 Hadoop, а mrunit-0.9.0-инкубационный-hadoop2.jar предназначен для работы с новой версией Hadoop MapReduce. Для этого поста и для всех остальных мы будем использовать версию hadoop-2.0 из релиза Cloudera  CDH4.1.1,  поэтому нам понадобится файл mrunit-0.9.0-инкубационный-hadoop2.jar. Я добавил MRUnit, JUnit и Mockito в качестве библиотек в Intellij (JUnit и Mockito находятся в том же каталоге, что и файлы JAR MRUnit). Теперь, когда мы настроили наши зависимости, давайте начнем тестирование.

Тестирование Mappers

Настройка для тестирования маппера очень проста и лучше всего объяснить, посмотрев сначала на некоторый код. Мы будем использовать пример объединения in-mapper из  предыдущего поста :

@Test
public void testCombiningMapper() throws Exception {
   new MapDriver<LongWritable,Text,Text,TemperatureAveragingPair>()
           .withMapper(new AverageTemperatureCombiningMapper())
           .withInput(new LongWritable(4),new Text(temps[3]))
           .withOutput(new Text("190101"),new TemperatureAveragingPair(-61,1))
           .runTest();
 }

Обратите внимание на свободный стиль API, который добавляет простоту создания теста. Чтобы написать свой тест, вы бы:

  1. Создание экземпляра класса MapDriver, параметризованного точно так же, как и тестируемый сопоставитель.
  2. Добавьте экземпляр Mapper, который вы тестируете, в вызове withMapper.
  3. При вызове withInput передайте ключ и входное значение, в этом случае LongWritable с произвольным значением и текстовый объект, содержащий строку из набора погодных данных NCDC, содержащегося в массиве String с именем ‘temps’, который был установлен ранее в тест (здесь не отображается, так как это отнимает от презентации).
  4. Укажите ожидаемый результат в вызове withOutput, здесь мы ожидаем объект Text со значением «190101» и объект TemperatureAveragingPair, содержащий значения -61 (температура) и 1 (количество).
  5. Последний вызов runTest передает указанные входные значения в маппер и сравнивает фактические выходные данные с ожидаемыми выходными данными, установленными в методе withOutput.

Следует отметить, что MapDriver допускает только один ввод и вывод для каждого теста. Вы можете вызывать withInput и withOutput несколько раз, если хотите, но MapDriver перезапишет существующие значения новыми, так что вы будете когда-либо тестировать только один ввод / вывод в любое время. Чтобы указать несколько входных данных, мы бы использовали MapReduceDriver, который был описан позже в нескольких разделах, но далее мы тестируем редуктор.

Тестирование редукторов

Тестирование редуктора происходит по той же схеме, что и тест картографа. Опять же, давайте начнем с примера кода:

@Test
public void testReducerCold(){
  List<TemperatureAveragingPair> pairList = new ArrayList<TemperatureAveragingPair>();
      pairList.add(new TemperatureAveragingPair(-78,1));
      pairList.add(new TemperatureAveragingPair(-84,1));
      pairList.add(new TemperatureAveragingPair(-28,1));
      pairList.add(new TemperatureAveragingPair(-56,1));

      new ReduceDriver<Text,TemperatureAveragingPair,Text,IntWritable>()
                .withReducer(new AverageTemperatureReducer())
                .withInput(new Text("190101"), pairList)
                .withOutput(new Text("190101"),new IntWritable(-61))
                .runTest();
    }

  1. Тест начинается с создания списка объектов TemperatureAveragingPair, которые будут использоваться в качестве входных данных для редуктора.
  2. Создается экземпляр ReducerDriver, и, как и MapperDriver, параметры точно соответствуют тестируемому редуктору.
  3. Далее мы передаем экземпляр редуктора, который мы хотим протестировать в вызове withReducer.
  4. В вызове withInput мы передаем ключ «190101» и объект pairList, созданный в начале теста.
  5. Затем мы указываем вывод, который мы ожидаем, что наш редуктор будет излучать, тот же ключ «190101» и IntWritable, представляющий среднее значение температур в списке.
  6. Наконец, вызывается runTest, который подает нашему редуктору указанные входные данные и сравнивает выходные данные редуктора с ожидаемыми выходными данными.

ReducerDriver имеет то же ограничение, что и MapperDriver, — не принимать более одной пары вход / выход. До сих пор мы тестировали Mapper и Reducer по отдельности, но мы также хотели бы протестировать их вместе в интеграционном тесте. Интеграционное тестирование может быть выполнено с использованием класса MapReduceDriver. MapReduceDriver также является классом, который используется для тестирования использования объединителей, пользовательских счетчиков или пользовательских разделителей.

Интеграционное тестирование

Для проверки совместной работы вашего преобразователя и преобразователя MRUnit предоставляет класс MapReduceDriver. Класс MapReduceDriver, как и следовало ожидать, с двумя основными отличиями. Сначала вы параметризуете типы ввода и вывода преобразователя, а также типы ввода и вывода редуктора. Поскольку типы выходных данных маппера должны соответствовать типам входов редуктора, вы получите 3 пары параметризованных типов. Во-вторых, вы можете предоставить несколько входов и указать несколько ожидаемых выходов. Вот наш пример кода:

@Test
public void testMapReduce(){

new MapReduceDriver<LongWritable,Text,
                      Text,TemperatureAveragingPair,
                      Text,IntWritable>()
                .withMapper(new AverageTemperatureMapper())
                .withInput(new LongWritable(1),new Text(temps[0]))
                .withInput(new LongWritable(2),new Text(temps[1]))
                .withInput(new LongWritable(3),new Text(temps[2]))
                .withInput(new LongWritable(4),new Text(temps[3]))
                .withInput(new LongWritable(5),new Text(temps[6]))
                .withInput(new LongWritable(6),new Text(temps[7]))
                .withInput(new LongWritable(7),new Text(temps[8]))
                .withInput(new LongWritable(8),new Text(temps[9]))
                .withCombiner(new AverageTemperatureCombiner())
                .withReducer(new AverageTemperatureReducer())
                .withOutput(new Text("190101"),new IntWritable(-22))
                .withOutput(new Text("190102"),new IntWritable(-40))
                .runTest();
    }

Как видно из приведенного выше примера, настройка такая же, как у классов MapDriver и ReduceDriver. Вы передаете экземпляры картографа, редуктора и, необязательно, комбайнера для тестирования. MapReduceDriver позволяет передавать несколько входов с разными ключами. Здесь массив ‘temps’ — это тот же массив, на который ссылаются в образце картографа, и содержит несколько строк из набора данных погоды NCDC, а ключами в этих строках выборки являются месяцы январь и февраль 1901 года, представленные как «190101» и « 190102 ″ соответственно. Этот тест успешен, поэтому мы получаем немного больше уверенности в правильности совместной работы нашего картографа и редуктора.

Вывод

Надеемся, мы доказали, насколько полезным может быть MRUnit для тестирования программ Hadoop. Я хотел бы завершить этот пост некоторыми своими наблюдениями. Хотя MRUnit делает модульное тестирование простым для кода мапперов и редукторов, представленные здесь примеры мапперов и редукторов довольно просты. Если ваша карта и / или сокращенный код начинают становиться более сложными, вероятно, лучше отделить код от инфраструктуры Hadoop и самостоятельно протестировать новые классы. Кроме того, поскольку класс MapReduceDriver полезен для тестирования интеграции, очень легко добраться до точки, когда вы больше не тестируете свой код, а саму платформу Hadoop, что уже было сделано. Я разработал собственную стратегию тестирования, которую я намерен использовать в будущем:

  1. Модульное тестирование карты / уменьшение кода.
  2. Возможно написать один интеграционный тест с классом MapReduceDriver.
  3. В качестве проверки работоспособности запустите задание MapReduce на установке с одним узлом (на моем ноутбуке), чтобы убедиться, что оно выполняется на платформе Hadoop.
  4. Затем запустите мой код на тестовом кластере на EC2, используя Apache Whirr в моем случае.

Рассказ о том, как настроить установку одного узла на моем ноутбуке (OSX Lion) и установка кластера на EC2 с помощью Whirr, сделает этот пост слишком длинным, поэтому я расскажу об этих темах в следующем. Спасибо за ваше время.