Тестировать и отлаживать многопоточные программы очень сложно. Теперь возьмите одни и те же программы и массово распределите их по нескольким JVM, развернутым на кластере машин, и сложность сойдет на нет. Один из способов преодоления этой сложности состоит в том, чтобы проводить тестирование изолированно и выявлять как можно больше ошибок локально MRUnit — это среда тестирования, которая позволяет вам тестировать и отлаживать задания Map Reduce изолированно, не раскручивая кластер Hadoop. В этом посте мы рассмотрим различные функции MRUnit, пройдя простую работу с MapReduce.
Допустим, мы хотим взять входные данные ниже и создать инвертированный индекс, используя MapReduce .
вход
www.kohls.com,clothes,shoes,beauty,toys www.amazon.com,books,music,toys,ebooks,movies,computers www.ebay.com,auctions,cars,computers,books,antiques www.macys.com,shoes,clothes,toys,jeans,sweaters www.kroger.com,groceries
Ожидаемый результат
antiques www.ebay.com auctions www.ebay.com beauty www.kohls.com books www.ebay.com,www.amazon.com cars www.ebay.com clothes www.kohls.com,www.macys.com computers www.amazon.com,www.ebay.com ebooks www.amazon.com jeans www.macys.com movies www.amazon.com music www.amazon.com shoes www.kohls.com,www.macys.com sweaters www.macys.com toys www.macys.com,www.amazon.com,www.kohls.com groceries www.kroger.com
ниже Mapper и Reducer, которые делают преобразование
public class InvertedIndexMapper extends MapReduceBase implements Mapper<LongWritable, Text, Text, Text> { public static final int RETAIlER_INDEX = 0; @Override public void map(LongWritable longWritable, Text text, OutputCollector<Text, Text> outputCollector, Reporter reporter) throws IOException { final String[] record = StringUtils.split(text.toString(), ","); final String retailer = record[RETAIlER_INDEX]; for (int i = 1; i < record.length; i++) { final String keyword = record[i]; outputCollector.collect(new Text(keyword), new Text(retailer)); } } } public class InvertedIndexReducer extends MapReduceBase implements Reducer<Text, Text, Text, Text> { @Override public void reduce(Text text, Iterator<Text> textIterator, OutputCollector<Text, Text> outputCollector, Reporter reporter) throws IOException { final String retailers = StringUtils.join(textIterator, ','); outputCollector.collect(text, new Text(retailers)); } }
Детали реализации не очень важны, но в основном Mapper получает строку за раз, разбивает строку и выдает пары ключ-значение, где Key — это категория продукта, а value — это веб-сайт, который продает продукт. Например, линия розничной торговли, категория 1, категория 2 будет отправлена как ( категория 1, продавец) и (категория a2, продавец) . Редуктор получает ключ и список значений, преобразует список значений в строку с разделителями-запятыми и выдает ключ и значение.
Теперь давайте использовать MRUnit для написания различных тестов для этой работы. Три основных класса в MRUnits: MapDriver для тестирования Mapper, ReduceDriver для тестирования Reducer и MapReduceDriver для сквозного тестирования MapReduce Job. Так мы настроим тестовый класс.
public class InvertedIndexJobTest { private MapDriver<LongWritable, Text, Text, Text> mapDriver; private ReduceDriver<Text, Text, Text, Text> reduceDriver; private MapReduceDriver<LongWritable, Text, Text, Text, Text, Text> mapReduceDriver; @Before public void setUp() throws Exception { final InvertedIndexMapper mapper = new InvertedIndexMapper(); final InvertedIndexReducer reducer = new InvertedIndexReducer(); mapDriver = MapDriver.newMapDriver(mapper); reduceDriver = ReduceDriver.newReduceDriver(reducer); mapReduceDriver = MapReduceDriver.newMapReduceDriver(mapper, reducer); } }
MRUnit поддерживает два стиля тестирования. Первый стиль состоит в том, чтобы сообщать фреймворку как входные, так и выходные значения, и позволить фреймворку делать утверждения, второй — это более традиционный подход, когда вы делаете утверждение самостоятельно. Давайте напишем тест, используя первый подход.
@Test public void testMapperWithSingleKeyAndValue() throws Exception { final LongWritable inputKey = new LongWritable(0); final Text inputValue = new Text("www.kroger.com,groceries"); final Text outputKey = new Text("groceries"); final Text outputValue = new Text("www.kroger.com"); mapDriver.withInput(inputKey, inputValue); mapDriver.withOutput(outputKey, outputValue); mapDriver.runTest(); }
В тесте выше мы сообщаем фреймворку обоим парам ключ и значение ввода и вывода, и фреймворк делает это за нас. Этот тест может быть написан более традиционным способом, как следует
@Test public void testMapperWithSingleKeyAndValueWithAssertion() throws Exception { final LongWritable inputKey = new LongWritable(0); final Text inputValue = new Text("www.kroger.com,groceries"); final Text outputKey = new Text("groceries"); final Text outputValue = new Text("www.kroger.com"); mapDriver.withInput(inputKey, inputValue); final List<Pair<Text, Text>> result = mapDriver.run(); assertThat(result) .isNotNull() .hasSize(1) .containsExactly(new Pair<Text, Text>(outputKey, outputValue)); }
Иногда Mapper генерирует несколько пар Key Value для одного ввода. MRUnit предоставляет свободный API для поддержки этого варианта использования. Вот пример
@Test public void testMapperWithSingleInputAndMultipleOutput() throws Exception { final LongWritable key = new LongWritable(0); mapDriver.withInput(key, new Text("www.amazon.com,books,music,toys,ebooks,movies,computers")); final List<Pair<Text, Text>> result = mapDriver.run(); final Pair<Text, Text> books = new Pair<Text, Text>(new Text("books"), new Text("www.amazon.com")); final Pair<Text, Text> toys = new Pair<Text, Text>(new Text("toys"), new Text("www.amazon.com")); assertThat(result) .isNotNull() .hasSize(6) .contains(books, toys); }
Вы пишете тест на снижение точно так же.
@Test public void testReducer() throws Exception { final Text inputKey = new Text("books"); final ImmutableList<Text> inputValue = ImmutableList.of(new Text("www.amazon.com"), new Text("www.ebay.com")); reduceDriver.withInput(inputKey,inputValue); final List<Pair<Text, Text>> result = reduceDriver.run(); final Pair<Text, Text> pair2 = new Pair<Text, Text>(inputKey, new Text("www.amazon.com,www.ebay.com")); assertThat(result) .isNotNull() .hasSize(1) .containsExactly(pair2); }
Наконец, вы можете использовать MapReduceDriver, чтобы протестировать ваши Mapper, Combiner и Reducer вместе как одно задание. Вы также можете передать несколько пар ключ-значение в качестве входных данных для вашей работы Тест ниже демонстрирует MapReduceDriver в действии
@Test public void testMapReduce() throws Exception { mapReduceDriver.withInput(new LongWritable(0), new Text("www.kohls.com,clothes,shoes,beauty,toys")); mapReduceDriver.withInput(new LongWritable(1), new Text("www.macys.com,shoes,clothes,toys,jeans,sweaters")); final List<Pair<Text, Text>> result = mapReduceDriver.run(); final Pair clothes = new Pair<Text, Text>(new Text("clothes"), new Text("www.kohls.com,www.macys.com")); final Pair jeans = new Pair<Text, Text>(new Text("jeans"), new Text("www.macys.com")); assertThat(result) .isNotNull() .hasSize(6) .contains(clothes, jeans); }