简体   繁体   English

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

[英]Unit testing domain objects with internal constructors and private setters

I have defined a Person class in a project called Tools.Client, which is a wrapper around a web service API. 我在名为Tools.Client的项目中定义了Person类,该类是Web服务API的包装。 Person is constructed with the XML returned by the service. 使用服务返回的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));
    }
  }

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

I have another class called Analyzer in a separate project called Tools.Analysis, which contains analysis logic to run against data retrieved from the API client. 在另一个名为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;
}

I would like to write a unit test for the Analyzer class's Analyze method, but I am not sure how I want to get around the following issues: 我想为Analyzer类的Analyze方法编写一个单元测试,但是我不确定如何解决以下问题:

  1. Person has an internal constructor method with an XElement parameter. Person有一个带有XElement参数的内部构造方法。 I do not want to create manual XElement objects in my unit tests. 我不想在单元测试中创建手动XElement对象。

  2. Person has private setters (as I think it should, I do not want users of Tools.Client altering data returned from the API). Person有私有设置器(我认为应该这样,我不希望Tools.Client的用户更改从API返回的数据)。 This problem is exacerbated by the additional dependency on Job, which has a similar structure. 对Job的额外依赖使问题更加严重,Job具有类似的结构。

I can think of a few solutions to this, but do not know which will be the most maintainable over time: 我可以想到一些解决方案,但是随着时间的推移,哪种方法最容易维护是不知道的:

  1. Create IPerson and IJob interfaces and use mocks or simple test implementations of these interfaces. 创建IPerson和IJob接口,并使用这些接口的模拟或简单测试实现。
  2. Expose public setters for easy testing (again, I don't really like this). 公开公众的二传手以便进行轻松测试(再次,我不是很喜欢)。 I think I could also use internal setters with the InternalsVisibleTo attribute (not as bad as public, but still not what I want). 我想我也可以将内部设置器与InternalsVisibleTo属性一起使用(不像public那样糟糕,但仍然不是我想要的)。
  3. Move XML parsing outside of the constructor , and have the constructor take the parameters firstName, lastName, jobs. 将XML解析移到构造函数之外 ,并让构造函数采用firstName,lastName,jobs参数。 The constructor can still be internal, I will just need to use the InternalsVisibleTo attribute on my assembly. 构造函数仍然可以是内部的,我只需要在程序集上使用InternalsVisibleTo属性即可。

I think you should move xml parsing from Person into a separate factory-like class, make Person immutable value-object-like class. 我认为您应该将xml解析从Person移到一个单独的类似于工厂的类中,使Person不可变的类似于值对象的类。 This way you won't need to mock them, you should be able to create real instances of Person and Job for testing Analyzer . 这样,您就无需模拟它们,您应该能够创建PersonJob真实实例以测试Analyzer

I agree that you don't want to make your setters public. 我同意您不想公开您的二传手。 You have an immutable class now, and you should not give that up lightly. 您现在拥有一门不变的课,您不应轻言放弃。

Interfaces on value objects are OK, but they are often overdesign. 值对象上的接口还可以,但是它们通常是过度设计的。 Avoid that if there is a better solution. 如果有更好的解决方案,请避免这种情况。

Go with option 3. Xml parsing is really a responsibility of another class; 使用选项3。Xml解析实际上是另一个类的责任。 Person should only be a data object. Person只能是数据对象。

If you can't do that, at the very least create a constructor without it. 如果您不能这样做,至少要创建一个没有它的构造函数。 This is not great because your constructors could possible diverge (they can't easily be made to call one another), but it's incrementally better than where you are. 这不是很好,因为您的构造函数可能会有所不同(它们不容易相互调用),但是它比您所处的位置好得多。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM