我已经拆分成功了,也就是得到了一个List对象。接下来,我要读取其中的数据,将其转化为一个SearchCriteria对象:
public object BindModel(ControllerContext controllerContext,ModelBindingContext bindingContext)
{
...
var tokenGroups =this.m_tokenizer.Tokenize(text);
return this.Build(tokenGroups);
}
private SearchCriteria Build(List tokenGroups)
{
var fieldTokens = tokenGroups.ToDictionary(
g => g[0].ToLowerInvariant(),
g => g.Skip(1).ToList());
var searchCriteria =new SearchCriteria();
List values;
if (fieldTokens.TryGetValue("keywords",outvalues))
{
searchCriteria.Keywords = values[0];
}
if (fieldTokens.TryGetValue("price",outvalues))
{
searchCriteria.Price =new PriceRange
{
Min = float.Parse(values[0]),
Max = float.Parse(values[1])
};
}
if (fieldTokens.TryGetValue("color",out values))
{
...
}
return searchCriteria;
}
在BindModel方法中得到了tokenGroups之后,便交由Build方法进行SearchCriteria对象的构建。首先,我先将 List对象转化为“字段”和“值”的对应关系,这样我们便可以使用keywords、price等字符串获取数据(也就是一个List对象),并生成SerachCriteria各属性所需要的值了。在这里,我们这一切都放在Build方法中的几个if里进行,但这很显然不是容易单元测试的方法。要知道,这里的代码看上去容易,但事实上每个if里的逻辑其实并不仅仅如此。例如,在输入不合法的情况下是容错,还是抛出异常?如果Min大于Max的情况下,是否直接将其交换再继续处理?因此,其实在每个if之中还会有if,还会有for等复杂的逻辑。对于这样的逻辑,我想要单元测试。
于是,我为List
public interface IConverter
{
object Convert(List
}
public class KeywordConverter : IConverter
{
public object Convert(List
{
return values[0];
}
}
public class PriceRangeConverter : IConverter
{
public object Convert(List
{
return new PriceRange
{
Min = float.Parse(values[0]),
Max = float.Parse(values[1]) }; }}
public class ColorConverter : IConverter
{
...
}
在这里,我为每个字段定义了一种转化器(而在实际开发过程中,我们可能也会为“每种类型”定义一个)。每个转化器对象均可独立的进行单元测试,其中复杂的边界条件,错误判断等等都是测试的目标。待几种转换器测试完毕,我们便可以重构SerachCriteriaBinder的Build方法:
private SearchCriteria Build(List tokenGroups)
{
var fieldTokens = tokenGroups.ToDictionary(
g => g[0].ToLowerInvariant(),
g => g.Skip(1).ToList());
var searchCriteria = new SearchCriteria();
List
if (fieldTokens.TryGetValue("keywords", out values))
{
searchCriteria.Keywords = (string)this.GetConverter("keywords").Convert(values);
}
if (fieldTokens.TryGetValue("price", out values))
{
searchCriteria.Price = (PriceRange)this.GetConverter("price").Convert(values);
}
if (fieldTokens.TryGetValue("color", out values))
{
searchCriteria.Colors = (Color)this.GetConverter("color").Convert(values);
}
return searchCriteria;
}
private IConverter GetConverter(string field)
{
// 使用if ... else或是字典
}