使用 Moq 测试递归函数

Test a recursive Function with Moq

我有一个递归方法,为了便于阅读,我试图简化它,因为它是一个混乱的方法。

我想获得 100% 的递归方法覆盖率。我正在努力设置将经过两次递归的模拟。然后退出。我想我需要使用回调而不是 returns,我不是 100% 确定。

    [TestMethod]
    public void TestRecursiveLoop()
    {
      var rootList = new List<MyObject> { new MyObject { Id = 999, ParentId = 1000 }, new MyObject { Id = 1000 } };
      var myObject = new MyObject { Id = 999 };

      var myMock = new Mock<IDBCLASS>();
      myMock.Setup(o => o.GETBYType(It.IsAny<Expression<Func<MyObject, bool>>>()))
        .Returns((Expression<Func<ShareholderTransaction, bool>> predicate) =>
        {
          return new List<MyObject>().Where(o => o.ParentId == myObject.Id).ToList();
        });
      // ?????????? not sure if this is correct
      var testRecursion = new TestRecursion(myMock.Object);
      testRecursion.Recursive(999, rootList);

    }
  }

  public class TestRecursion
  {
    private IDBCLASS dbClass;

    public TestRecursion(IDBCLASS dbClass)
    {
      this.dbClass = dbClass;
    }

    public void Recursive(int id, List<MyObject> list)
    {
      var parentObject = this.dbClass.GETBYType<MyObject>(x => x.ParentId == id).FirstOrDefault();

      if (parentObject == null)
      {
        return;
      }

      list.Add(parentObject);
      Recursive(parentObject.Id, list);
    }
  }

  public class MyObject
  {
    public int ParentId { get; set; }

    public int Id { get; set; }

  }

  public class DBCLASS : IDBCLASS
  {

    public List<T> GETBYType<T>(Expression<Func<T, bool>> p) where T : class
    {
      // return something from DB;
    }

  }
  public interface IDBCLASS
  {
    List<T> GETBYType<T>(Expression<Func<T, bool>> p) where T : class;
  }

你基本上有两个不同的选择:

要么你有一个很大的 Arrange 部分(如在 AAA 中,Arrange Act Assert),要么你使用了一个小缺点:

大排列方法

正如@Brian 在对您的问题的评论中所说,您可以在每次调用时将 GETBYType 设置为 return 不同的值。

他提出的方法可行,但是,存在一种简单、更清晰的方法:您可以使用 SetupSequence 方法。

本质上,用法应该是这样的:

var myMock = new Mock<IDBCLASS>();
myMock.SetupSequence(o => o.GETBYType(It.IsAny<Expression<Func<MyObject, bool>>>()))
    .Returns(new MyObject { Id = 999, ParentId = 1000 })
    .Returns(new MyObject { Id = 1000 } )
    .Returns(null);

自我黑客

还有另一种方法,不需要您对整个递归进行复杂的排列。它背后的想法是只断言 递归 继续正确。

我们在这里面临的问题是,由于是递归调用,我们无法对被测对象进行断言。为了解决这个问题,我们可以利用一些技巧。它不是很干净,但它可以让你大大减少测试的数量。

诀窍是在您的 TestRecursion class 中注入类型 TestRecursion 的对象。那时,您将有两个构造函数:一个无参数构造函数和一个接受 TestRecursion 实例的构造函数。接受 TestRecursion 对象的构造函数将把它作为一个字段(我们称它为 this.self),以便调用该对象的递归方法。

现在,接受 TestRecursion 对象的构造函数应该 永远不会 被生产代码使用 - 只能被测试使用,except 在一个地方:无参数构造函数将调用构造函数期望 TestRecursion 对象,将 this 作为参数传递。

除此异常外,生产代码将始终调用无参数构造函数。不幸的是,这个无参数构造函数不容易测试,这是变通方法的一大缺点。但是,现在可以很容易地从测试中注入模拟的 TestRecursion 并验证递归方法是否使用正确的参数调用了正确的次数。

当然,Recursive方法的实现应该稍微修改一下,替换下面一行

  Recursive(parentObject.Id, list);

  this.self.Recursive(parentObject.Id, list);

到那时,测试停止条件也应该容易得多。

希望这对您有所帮助。