技术开发 频道

我的TDD实践:可测试性驱动开发

  我已经拆分成功了,也就是得到了一个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 values);

  }

  public class KeywordConverter : IConverter

  {

  public object Convert(List values)

  {

  return values[0];

  }

  }

  public class PriceRangeConverter : IConverter

  {

  public object Convert(List values)

  {

  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 values;

  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或是字典

  }

0
相关文章