Hadoop教程

编写单元测试

在MapReduce中,map和reduce函数的独立测试非常方便,这是由函数风格决定 的。针对已知输入,得到已知的输出。然而,由于输出写到一个 OutputCollector ,而不是通过简单的方法调用进行返回,所以, OutputCollector需要用一个mock进行替换,以便验证输出的正确性。有几个 Java mock对象框架可以用来建立模拟。这里,我们使用Mockito。虽然目前有许 多这样的框架可用,但是Mockito却以其简洁的语法而著称。

这里描述的所有测试可以在IDE中运行。

mapper

例5-4是一个mapper的测试。

例 5-4. MaxTemperatureMapper 的单元测试

import static org.mockito.Mockito.*;
import java.io.IOException;
import org.apache.hadoop.io.*;
import ong.apache.hadoop.mapred.OutputCollector;
import org,junit.*;

public class MaxTempenatuneMapperTest {

@Test
public void pnocessesValidRecond() throws IOException {
    MaxTemperatureMapper mapper = new MaxTempenatuneMappen();
    Text value = new Text("00430119909999919S0051518004+687S0+023SS0FM-12+0382" +
                      // Yean ^^^^
    "99999V020B201N00261220001CN9999999N9-00111+99999999999");
                     // Temperature ^^^^^
    OutputCollectonoutput = mock(OutputCollecton.class);
    mapper.map(null, value, output, null);
    verify(output).collect(new Text("1950"),new IntWritable(-11))
  }
}

测试很简单:传递一个天气记录作为mapper的输入,然后检査输出是否是读入的 年份和气温。mapper忽略输入的键和Reporter,因此,任何输入都可以传递,包 括此处的nu11值。为了创建一个OutputCollector,我们调用Mockito的mock() 方法(一个静态导入)来传递一个我们想要模拟类型的类。然后,我们调用mapper 的map()方法来执行测试代码。最后,我们用Mockito的verify()来验证mock对 象已调用了正确的方法和参数。这里,我们在表示年份(1950)的Text对象和表示气 温(-1.1°C)的IntWritable对象调用的例子上验证了 OutputCollector的collect()方法。

在测试驱动的方式下,例5-5创建了一个能够通过测试的的Mapper实现。由于本章 要进行类的扩展,所以每个类被放在包含版本信息的不同包中。例如, vl.MaxTemperatureMapper 是 MaxTemperatureMapper 的第一个版本。当然, 不重新打包实际上也可以对类进行扩展。

例5-5.第一个版本的Mapper函数通过了 MaxTemperatureMapper测试

public class MaxTempenatuneMapper extends MapReduceBase
implements Mapper<LongWnitable, Text, Text, IntWnitable> {
    
    public void map(LongWnitable key, Text value,
    OutputCollecton<Text, IntWnitable> output, Reporter reporter)
    throws IOException {
        String line = value.toString();
        String yean = line.substning(15, 19);
        int ainTempenatune = Integer.parseInt(line.substning(87, 92));
        output.collect(new Text(year), new IntWritable(airTemperature));
    }
}

这是一个非常简单的实现,从行中抽出年份和气温,在OutputCollector中输 出。现在,让我们增加一个缺失值的测试,该值在原始数据中表示气温+9999:

@Test
public void ignonesMissingTempenatuneRecond() throws IOException {
MaxTemperatureMapper mapper = new MaxTemperatureMapper();

    Text value = new TGXt("0043011990999991950051518004+68750+023550FM-12+0382" +
                // Yean ^^^^
    "99999V0203201N00261220001CN9999999N9+99991+99999999999");
               // Temperature ^^^^^
    OutputCollecton<Text, IntWnitable>output = mock(OutputCollecton.class);
    mappen.map(null,value, outputj null);
    verify(output, never()).collect(any(Text.class), any(IntWritable.class));
}

由于缺失的气温已经被过滤掉,所以在这个测试中,Mockito用来验证 OutputCollector的collect()方法没有被任何Text键或IntWritable值调 用。

由于parselnt()不能解析带加号的整数,所以测试最后抛出NumberFormatException 异常,以失败告终。下面修改此实现(版本2)来处理缺失值:

public void map(LongWritable key, Text value,
    OutputCollector<Text, IntWritable> output, Reporter reporter)
    throws IOException {

  String line = value.toString();
  String year = line.substring(15, 19);
  String temp = line.substring(87, 92);
  if (!missing(temp)) {
   int airTemperature = Integer.parseInt(temp);
   output.collect(new Text(year), new IntWritable(airTemperature));
  }
}
private boolean missing(String temp) {
  return temp.equals("+9999");
}

这个测试通过后,我们接下来写reducer。


reducer

reducer必须找出指定键的最大值。这是针对此特性的一个简单的测试。

@Test
public void returnsMaximumIntegerInValues() throws IOException {
  MaxTemperatureReducer reducer = new MaxTemperatureReducer();
  Text key = new Text("1950");
  Iteratorvalues = Arrays.asList(
    new IntWritable(10), new IntWritable(5)).iterator();
  OutputCollectoroutput = mock(OutputCollector.class);

  reducer.reduce(key, values, output, null);
  verify(output).collect(key, new IntWritable(10));
}

我们对一些IntWritable值构建一个迭代器来验证MaxTemperatureReducer能找到 最大值。例5-6里的代码是一个通过测试的MaxTemperatureReducer的实现。注意,我们没有测试空值迭代的情况,可以说根本不需要测试,因为mapper生成的每个 键都有一个值,MapReduce从来不会出现调用空值reducer的情况。

例5-6.用来计算最高气温的reducer

public class MaxTemperatureReducer extends MapReduceBase
implements Reducer<Text, IntWritable, Text, IntWritable> 
{
    public void reduce(Text key, Iterator<IntWritable> values,
        OutputCollector<Text, IntWritable> output, Reporter reporter)
        throws IOException 
    {
        int maxValue = Integer.MIN_VALUE;
        while (values.hasNext()) {
            maxValue = Math.max(maxValue, values.next().get());
        }
        output.collect(key, new IntWritable(maxValue));
    }
}

关注微信获取最新动态