【Hadoop】15-用MRUnit编写单元测试

在MapReduce中,map函数和reduce函数的独立测试非常方便,这是由函数风格决定的。MRUnit(http://mrunit.apache.org/)是一个测试库,它便于将已知的输人传递给mapper或者检查reducer的输出是否符合预期。MRUnit与标准的测试执行框架(如JUnit)—起使用,因此可以在正常的开发环境中运行MapReduce作业的测试。

1.关于Mapper

范例是一个mapper的测试。MaxTemperatureMapper的单元测试
import java.io.IOException;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mrunit.mapreduce.MapDriver;
import org.junit.*;
public class MaxTemperatureMapperTest{
    @Test
    public void processesVaIidRecord() throws IOException,InterruptedException{
        Text value=new Text(".....");
        new MapDriver<LongWritable,Text,Text,IntWritable>()
            .withMapper(new MaxTemperatureMapper())
            .withlnput(new LongWritable(0),value)
            .withOutput(new Text("1959"),new IntWritabIe(-11))
            .runTest();
    }
}
测试很简单:传递一个天气记录作为mapper的输人,然后检查输出是否是读人的年份和气温。由于测试的是mapper,所以可以使用MapDriver。在调用runTest()方法执行这个测试之前,在test(MaxTemperatureMapper)下配置mapper、输人key和

值、期望的输出key(1959,表示年份的Text对象)和期望的输出值(-11℃,表示温度的lntwritable)。如果mapper没有输出期望的值,MRUnit测试失败。注意,由于mapper忽略输人key,因此,输人key可以设置为任何值。在测试驱动的方式下,范例创建了一个能够通过测试的Mapper实现。由于本章要进行类的扩展,所以每个类被放在包含版本信息的不同包中。例如,v1.MaxTemperatureMapper是MaxTemperatureMapper的第一个版本。当然,不重新打包实际上也可以对类进行扩展。

范例,通过了MaxTemperatureMapper测试的第一个版本Mapper函数

public class MaxTemperatureMapper extends Mapper<LongWritable,Text,IntWritab1e>{
    @Override
    public void map(LongWritable key,Text value,Context context)throws IOException,InterruptedException{
        String line=value.toString();
        String year=line.substring(15,19);
        int airTemperature=Integer.parseInt(line.substring(87,92));
        context.write(new Text(year),new IntWritable(airTemperature));
    }
}

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

@Test
public void ignoresMissingTemperatureRecord()throws IOException,InterruptedException{
    Textvalue=newText("30119999919551518-+68750+9235FM.12+9382"+
                //Year^
        "99999v9293201N09261229991(N9999999N9+99991+99999999999");
                //Temperature
    new MapDriver<LongWritab1e,Text,Text,IntWritable>()
        .withMapper(new MaxTemperatureMapper())
        .withlnput(new LongWritabIe(0),value)
        ·runTest();
}

根据with0utput()被调用的次数,MapDriver能用来检查0、1或多个输出记录。在这个测试中由于缺失温度的记录已经被过滤掉,该测试保证对于这种特定的输人值不产生任何输出。

由于没有考虑到+9999这样一种特殊情况,新测试以失败告终。与其在mapper中加人更多的逻辑考虑,还不如给出一个解析类来封装解析逻辑更有意义。详情可以参见下面范例。

范例,该类解析NCDC格式的气温记录

public class NcdcRecordParser{
    private static final int MISSING_TEMPERATURE=9999;
    private String year;
    private int airTemperature;
    private String quality;
    public void parse(String record){
        year=record.substring(15,19);
        String airTemperatureString;
        //RemoveLeadingplussignasparserntdoesn'tethemre刁0v0乃
        if(record.charAt(87)=='+'){
            airTemperatureString=record.substring(88,92);
        }else{
            airTemperatureString=record.substring(87,92);

        }
        airTemperature=lnteger.parselnt(airTemperatureString);
        quality=record.substring(92,93);
    }
    public void parse(Text record){
        parse(record·toString();
    }
    public boolean isValidTemperature(){
        return airTemperature!=MISSING_TEMPERATURE&&quality.matches("[04159]");
    }
    public String getYear(){
        return year;
    }
    public int getAirTemperature(){
        return airTemperature;
     }
}

最终的mapper(第二个版本)相当简单。它只需要调用解析类的parse()方法,就能对输人行中感兴肉字段进行解析,用isValidTemperature()查询方法可以检查是否是合法气温,如果是,则使用解析类的获取方法得到年份和值。需要注意是,我们在isValidTemperature()中检查是否有遗漏气温值的同时,也对质量状态字段进行检查,以便过滤掉不准确的气温读取值。

创建解析类的另一个好处是:相似作业的mapper不需要重写代码。并且,对于更多的有针对性的测试而言,该方法也提供了一个机会,即可以直接针对解析类编写单元测试。

范例,这个mapper使用utility类来解析记录

public class MaxTemperatureMapper extends Mapper<LongWritable,Text,Text,IntWritable>{
    private NcdcRecordParser parser=newNcdcRecordParser();
    @Override
    public void map(LongWritable key,Text value,Context context)throws IOException,InterruptedException{
        parser.parse(value);
        if(parser.isValidTemperature()){
            context.write(newText(parser.getYear()),new IntWritabIe(parser.getAirTemperature()));
        }
    }
}

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

2.关于Reducer

reducer必须找出指定键的最大值。这是针对此特性的一个简单的测试,其中使用了一个ReduceDriver。
@Test
public void returnsMaximumIntegerInVaIues() throws IOException,InterruptedException{
    newReduceDriver<Text,IntWritable,Text,IntWritabIe>()
        .withReducer(new MaxTemperatureReducer())
        .withInputKey(newText("1950"),Arrays.asList(newIntWritable(10),new IntWritable(5)))
        .withOutput(newText("1950"),newIntWritab1e(1e))
        ·runTest();
}

我们对一些IntWritable值构建一个迭代器来验证MaxTemperatureReducer能找到最大值。范例里的代码是一个通过测试的MaxTemperatureReducer的实现。

范例,用来计算最高气温的reducer

public class MaxTemperatureReducer extends Reducer<Text,IntWritable,Text,IntWritable>{
    @Override
    public void reduce(Text key,IterabIe<IntWritabIe>values,Context context)throws IOException,InterruptedException{
        int maxValue=Integer.MIN_VALUE;
        for(IntWritabIe value:values){
            maxValue=Math.max(maxVaIue,value.get());
            context.write(key,new IntWritabIe(maxVaIue));
        }
    } 
}

猜你喜欢

转载自blog.csdn.net/shenchaohao12321/article/details/80302886