单元测试具有内部构造函数和私有setter的域对象

时间:2013-03-28 03:04:55

标签: c# unit-testing mocking

我在名为Tools.Client的项目中定义了一个Person类,它是Web服务API的包装器。 Person是使用服务返回的XML构建的。

public class Person
{
  internal Person(XElement personElement)
  {
    this.FirstName = personElement.Element("first_name").Value;
    this.LastName = personElement.Element("last_name").Value;

    this.Jobs = new List<Job>();
    foreach (var jobElement in personElement.Elements("jobs"))
    {
      this.Jobs.Add(new Job(jobElement));
    }
  }

  public string FirstName {get; private set;}
  public string LastName {get; private set;}
  public ICollection<Job> {get; private set;}
}

我在另一个名为Tools.Analysis的项目中有另一个名为Analyzer的类,它包含针对从API客户端检索的数据运行的分析逻辑。

private readonly ICollection<Person> _people;
public Analyzer(ICollection<Person> people)
{
  _people = people;
}

public AnalysisResult Analyze()
{
  var result = new AnalysisResult();

  foreach (var person in _people)
  {
    // do some analysis, store data in the result
  }

  return result;
}

我想为Analyzer类的Analyze方法编写单元测试,但我不确定如何解决以下问题:

  1. Person有一个带有XElement参数的内部构造函数方法。我不想在单元测试中创建手动XElement对象。

  2. Person有私有的setter(我认为它应该,我不希望Tools.Client的用户改变从API返回的数据)。 Job的附加依赖性加剧了这个问题,Job具有类似的结构。

  3. 我可以想到一些解决方案,但不知道哪个是最容易维护的:

    1. 创建IPerson和IJob接口并使用这些接口的模拟或简单测试实现。
    2. 公开setter 以便于测试(同样,我真的不喜欢这个)。我想我也可以使用带有InternalsVisibleTo属性的内部setter(不像公众那么糟糕,但仍然不是我想要的)。
    3. 在构造函数之外移动XML解析,并让构造函数获取参数firstName,lastName,jobs。构造函数仍然可以是内部的,我只需要在程序集中使用InternalsVisibleTo属性。

2 个答案:

答案 0 :(得分:4)

我认为你应该将xml解析从Person移动到一个单独的类似工厂的类中,使Person不可变的值类对象类。这样您就不需要模拟它们,您应该能够创建PersonJob的真实实例来测试Analyzer

答案 1 :(得分:0)

我同意你不想让你的主持人公开。你现在有一个不可变的课程,你不应该轻易放弃。

值对象的接口没问题,但它们经常被过度设计。如果有更好的解决方案,请避免使用。

使用选项3. Xml解析实际上是另一个类的责任; Person应该只是一个数据对象。

如果你不能这样做,至少要创建一个没有它的构造函数。这不是很好,因为你的构造函数可能会分歧(它们不能轻易地相互调用),但它比你的位置更好。

public class Person
{
  internal Person(XElement personElement)
  {
    this.FirstName = personElement.Element("first_name").Value;
    this.LastName = personElement.Element("last_name").Value;

    this.Jobs = new List<Job>();
    foreach (var jobElement in personElement.Elements("jobs"))
    {
      this.Jobs.Add(new Job(jobElement));
    }
  }

  internal Person(string firstName, string lastName, ICollection<Job> jobs)
  {
     //set properties
  }

  public string FirstName {get; private set;}
  public string LastName {get; private set;}
  public ICollection<Job> {get; private set;}
}