【问题标题】:How do I unit test a nested foreach loop?如何对嵌套的 foreach 循环进行单元测试?
【发布时间】:2014-05-02 23:06:33
【问题描述】:

我有一个函数,它遍历父对象列表并在 for-each 循环中为每个父对象获取子对象。要获取子对象的列表,该函数调用另一个函数。这是函数:

public IEnumerable<IParentObject> GetParentAndChildObjects()
{
    var parentObjects = new List<ParentObject>();

    foreach (var item in _dbRepository.GetParentObjects())
    {
        var parentObject = new ParentObject() { Id = item.ID, Name = item.Name };
        foreach (var subItem in _dbRepository.GetChildObjects(item.ID))
        {
            parentObjects.Children.Add(new ChildObject() { Id = subItem.ID, Name = subItem.Name });
        }
    }
    return parentObjects;
}

如何为这个函数编写单元测试?我正在使用 Moq 进行模拟。

【问题讨论】:

  • 离题:您永远不会将parentObject 添加到parentObjects。你在parentObjects 上调用Children 而不是新创建的parentObject。所以你的代码根本不会编译(除非你有自己的List&lt;T&gt; 实现和Children 属性)。
  • 你嘲笑_dbRepository

标签: c# unit-testing moq


【解决方案1】:

您可以使用的一种方法是以与私有方法相同的方式测试内部循环。也就是说,你不(至少,不直接)。

验证GetParentAndChildObjects返回的结果,而不是测试循环本身。

通过为_dbRepository 指定一个测试替身,您可以完全控制插入到parentObjects 列表中的对象的详细信息。因此,您的测试可以验证返回的对象列表是否符合预期。

Mark Seemann describes this approach well:

在多次回答了关于何时使用 [stubs 和何时使用 mocks] 的问题后,我得出了这个基于命令查询分离 (CQS) 语言的简单规则:

  • 对命令使用模拟

  • 使用存根进行查询

这很有意义,因为命令都是关于副作用的,而 Mocks 都是关于行为验证的:也就是说,副作用发生了。另一方面,存根的存在主要是为了“发出快乐的声音”,它们必须这样做的一种方式是在需要返回数据时从依赖项返回数据。

【讨论】:

  • 最佳答案恕我直言,测试方法的功能,而不是底层代码结构。
【解决方案2】:

类似这样的:

var repo = new Mock<MyRepository>();
repo.Setup(r => r.GetParentObjects()).Returns(... a list with a couple of parent objects ...);
repo.SetUp(r => r.GetChildObjects(... id of first parent ...)).Returns(... list of child objects ...);
repo.SetUp(r => r.GetChildObjects(... id of secondparent ...)).Returns(... different list of child objects ...);

var result = new MyClass(repo.Object).GetParentAndChildObjects();

... assertions to check 2 parents with appropriate children returned ...

【讨论】:

    【解决方案3】:

    通常,您会为内部循环创建一个函数(无论如何,为了可读性和嵌套,这可能是一件好事)。然后,一旦该功能到位,您就可以为它创建单元测试,就像为任何其他方法一样。

    如果由于某种原因您不想使用"Extract Method" refactoring(可以使用 Resharper 或其他重构工具自动化),恐怕您运气不好,因为单元测试需要有一个入口点测试,您不能在其他方法的中间输入。

    您可能会考虑的一件事,虽然它需要更多维护,但有一个用于单元测试的调试版本和一个内联循环的优化版本,使用#if。如果您在 .NET 4.5 或更高版本上,您可以简单地将内部循环作为方法提取并使用aggressive inlining of the method,并且您没有提取方法的(潜在)损失或性能的高维护开销。

    【讨论】:

      猜你喜欢
      • 2021-08-02
      • 1970-01-01
      • 1970-01-01
      • 2019-05-05
      • 1970-01-01
      • 2015-01-25
      • 1970-01-01
      • 2021-09-24
      • 1970-01-01
      相关资源
      最近更新 更多