关于c#:FakeItEasy,伪造一个来自子类的父母虚拟方法

FakeItEasy, Fake a parents virtual method from the child class

我试图在没有成功的情况下(使用FakeItEasy)从孩子那里伪造对家长公共虚拟验证方法的调用。我有一个基类,它为类似的命令类验证简单命令(为了简单起见,我减少了代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  public class CanSetSettings<TSettings> : IValidationhandler<TSettings> where TSettings : ISetting
  {
    protected readonly IFooRepository Repository;
    protected List<ValidationResult> Results;

    public CanSetSettings(IFooRepository repository)
    {
      if (Repository== null)
        throw new ArgumentNullException("IFooRepository","IFooRepository is needed to validate the command.");

      repository = repository;
      Results = new List<ValidationResult>();
    }

    public virtual ICollection<ValidationResult> Validate(TSettings settings)
    {
      if (settings == null)
      {
        Results.Add(new ValidationResult("Settings","The command to validate cannot be missing."));
        return Results;
      }

      if (Repository.Query(settings.Location) == null)
        Results.Add(new ValidationResult("Location","No Location was found for your settings."));

      return Results;
    }

然后我有从这个基类继承的子类,然后通过重写Validate(简化了我的代码)来实现它们的特定逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CanSetSpecificSetting : CanSetSettings<SetSpecificSettings>, IValidationhandler<SetSpecificSettings>
  {
    public CanSetSpecificSetting (IFooRepository repo)
      : base(repo)
    { }

    public override ICollection<ValidationResult> Validate(SetSpecificSettings command)
    {
      base.Validate(command); // TODO determine if this call was made

    // ... other logic here

      return results;
    }
  }

我在单元测试中尝试过这种方法,它只配置对子类的方法调用,而不能配置父类。如何配置fake来调用子类和伪造父基类方法?谢谢您。

1
2
3
4
5
6
7
8
9
      var _repository = A.Fake<IFooRepository>();
      var _validator = A.Fake<CanSetSpecificSetting>(opt => opt.WithArgumentsForConstructor(new object[] { _repository }));

      A.CallTo(() => _validator.Validate(_cmd)).CallsBaseMethod().Once();

      _validator.Validate(_cmd);

      // this passes, but only because it knows about the child call
      A.CallTo(() => _validator.Validate(_cmd)).MustHaveHappened(Repeated.Exactly.Once);


不,我不认为你能轻易地做你想做的事,用假货。我甚至认为你不应该那样做。

但是,通过将基调用封装到子类中的模板方法中,可以实现类似的功能。只需更改CanSetSpecificSetting,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CanSetSpecificSetting : CanSetSettings<SetSpecificSettings>, IValidationhandler<SetSpecificSettings>
{
    public CanSetSpecificSetting (IFooRepository repo)
    : base(repo)
    { }

    public override ICollection<ValidationResult> Validate(SetSpecificSettings command)
    {
        BaseValidate(command); // << Instead of calling base.Validate(command).

        // ... other logic here

        return results;
    }

    // This is the template method. You MUST declare it as virtual.
    protected virtual ICollection<ValidationResult> BaseValidate(SetSpecificSettings command)
    {
        return base.Validate(command);
    }
}

然后像这样更改测试:

1
2
3
4
5
6
7
8
9
10
11
var _repository = A.Fake<IFooRepository>();
var _validator = A.Fake<CanSetSpecificSetting>(opt => opt.WithArgumentsForConstructor(new object[] { _repository }));

A.CallTo(() => _validator.Validate(_cmd)).CallsBaseMethod().Once();

// *** ADD THIS LINE *** must configure it this way because the template method is protected - we don't want to make it public!
A.CallTo(_validator).WhereMethod(x => x.Name =="BaseValidate").Returns("whatever-you-want-to-return");

_validator.Validate(_cmd);

A.CallTo(() => _validator.Validate(_cmd)).MustHaveHappened(Repeated.Exactly.Once);

再说一遍,这很难看。我能想象的唯一的情况是,这样做是可以的,将测试添加到您打算(并且将)尽快重构的一些遗留代码中。

希望它有帮助。


有趣的问题。对同一对象中的另一个方法使用Fakeitasy的a.callto()的变体,但它不是"同一对象中的另一个方法",而是"同一对象中的同一方法"(几乎是同一对象中的同一方法)。

我认为这是不可能的。当fakeitiasy伪造它时,它会创建一个子类,该子类有自己的Validate定义。我们可以询问这个方法,但是没有办法修改任何基类的行为,因此我们不能询问类是否调用了它的基。

我认为这种测试验证器的方法有点不寻常,并且会推荐不同的路径。如果您可以选择重新构造生产代码,那么可以组成验证器,而不是在层次结构中定义它们。或者您可以让基础(公共)功能驻留在由validate调用的其他方法中。然后您可以询问该方法,尽管我不推荐后一种方法。

如果您不能(或者不愿意)重构生产代码,另一种方法是测试具体验证器的整体行为。与其查看CanSetSpecificSetting是否调用其基,不如考虑检查它在不同的条件下是否正常工作,例如当存储库为空时。不仅验证起来更容易(可能),而且这种方法可以提供更好的结果,确保整个系统按照您希望的方式运行,而不仅仅是某些方法对其父对象进行某些调用。

根据测试框架的不同,通过参数化基本测试用例,然后为每个具体实现执行它们,可以非常容易地生成所有这些测试。