使用内部构造函数和私有设置器对域对象进行单元测试

本文关键字:对象 单元测试 设置 内部 构造函数 | 更新日期: 2023-09-27 18:06:27

我在一个名为Tools的项目中定义了一个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的单独项目中有另一个名为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有私人设置(我认为它应该,我不希望工具的用户。客户端修改从API返回的数据)。对Job的额外依赖加剧了这个问题,因为Job具有类似的结构。

我可以想到一些解决方案,但不知道哪一个将是最可维护的随着时间的推移:

  1. 创建IPerson和IJob接口并使用这些接口的模拟或简单测试实现。
  2. 暴露公共设置以方便测试(再次,我真的不喜欢这样)。我想我也可以使用内部设置与InternalsVisibleTo属性(不像公共那么糟糕,但仍然不是我想要的)。
  3. 将XML解析移出构造函数,并让构造函数接受参数firstName、lastName、jobs。构造函数仍然可以是内部的,我只需要在程序集中使用InternalsVisibleTo属性。

使用内部构造函数和私有设置器对域对象进行单元测试

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

我同意你不想让你的setter公开。你现在有了一个不可变的类,你不应该轻易放弃它。

值对象上的接口是可以的,但是它们经常被过度设计。如果有更好的解决方案,请避免。

选择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;}
}