Статьи

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

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

1
2
3
4
5
6
7
8
@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, который был описан позже в нескольких разделах, но далее мы тестируем редуктор.

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@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 пары параметризованных типов. Во-вторых, вы можете предоставить несколько входов и указать несколько ожидаемых выходов. Вот наш пример кода:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
@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, сделает этот пост слишком длинным, поэтому я расскажу об этих темах в следующем. Спасибо за ваше время.

Ресурсы

Ссылка: Тестирование программ Hadoop с MRUnit от нашего партнера по JCG Билла Бекака в блоге Randomечет о кодировании .