编写单元测试
在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));
}
}