C# 使用内部构造函数和私有setter对域对象进行单元测试

C# 使用内部构造函数和私有setter对域对象进行单元测试,c#,unit-testing,mocking,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

我在一个名为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;}
}
公共类人物
{
内部人员(XElement人员Element)
{
this.FirstName=personElement.Element(“first_name”).值;
this.LastName=personElement.Element(“last_name”).Value;
this.Jobs=新列表();
foreach(personElement.Elements(“作业”)中的var jobElement)
{
this.Jobs.Add(newjob(jobElement));
}
}
公共字符串名{get;private set;}
公共字符串LastName{get;private set;}
公共ICollection{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;
}
private readonly ICollection\u个人;
公共分析器(ICollection人员)
{
_人=人;
}
公共分析结果分析()
{
var result=新的AnalysisResult();
foreach(var人员在_人员中)
{
//做一些分析,在结果中存储数据
}
返回结果;
}
我想为Analyzer类的Analyze方法编写一个单元测试,但我不确定如何绕过以下问题:

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

  • Person有私有setter(正如我认为应该的那样,我不希望用户使用Tools.Client修改API返回的数据)。这一问题因对工作的额外依赖而加剧,而工作具有类似的结构

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

  • 创建IPerson和IJob接口并使用这些接口的模拟或简单测试实现
  • 公开公共设置程序以便于测试(再说一遍,我真的不喜欢这样)。我想我也可以使用带有InternalsVisibleTo属性的内部setter(不像public那个样糟糕,但仍然不是我想要的)
  • 将XML解析移到构造函数之外,并让构造函数使用参数firstName、lastName和jobs。构造函数仍然可以是内部的,我只需要在程序集上使用InternalsVisibleTo属性

  • 我认为应该将xml解析从
    Person
    移动到一个单独的类似工厂的类中,使
    Person
    成为类似于类的不可变值对象。这样,您就不需要模拟它们了,您应该能够创建
    Person
    Job
    的真实实例来测试
    Analyzer
    我认为您应该将xml解析从
    Person
    移动到一个单独的类似工厂的类中,使
    Person
    成为类似于类的不可变值对象。这样你就不需要模仿他们了,你应该能够为测试
    分析器创建
    Person
    Job
    的真实实例。我同意你不想公开你的设置程序。您现在有了一个不可变的类,您不应该轻易放弃它

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

    选择3。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));
        }
      }
    
      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;}
    }
    
    公共类人物
    {
    内部人员(XElement人员Element)
    {
    this.FirstName=personElement.Element(“first_name”).值;
    this.LastName=personElement.Element(“last_name”).Value;
    this.Jobs=新列表();
    foreach(personElement.Elements(“作业”)中的var jobElement)
    {
    this.Jobs.Add(newjob(jobElement));
    }
    }
    内部人员(字符串名、字符串名、ICollection作业)
    {
    //设置属性
    }
    公共字符串名{get;private set;}
    公共字符串LastName{get;private set;}
    公共ICollection{get;private set;}
    }
    
    我同意你不想公开你的设定者。您现在有了一个不可变的类,您不应该轻易放弃它

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

    选择3。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));
        }
      }
    
      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;}
    }
    
    公共类人物
    {
    内部人员(XElement人员Element)
    {
    this.FirstName=personElement.Element(“first_name”).值;
    this.LastName=personElement.Element(“last_name”).Value;
    this.Jobs=新列表();
    foreach(personElement.Elements(“作业”)中的var jobElement)
    {
    this.Jobs.Add(newjob(jobElement));
    }
    }
    内部人员(字符串名、字符串名、ICollection作业)
    {
    //设置属性
    }
    公共字符串名{get;private set;}
    公共字符串LastName{get;private set;}
    公共ICollection{get;private set;}
    }
    
    同意。Xml解析不应该是Person类的责任。违反SRP,IMHO。我想我是在写问题时得出这个结论的,但有一些验证是很好的,谢谢:)同意。Xml解析不应该是Person类的责任。违反SRP,IMHO。我想我是在写问题时得出这个结论的,但是有一些验证是很好的,谢谢:)