你如何正确模拟 IEnumerable<T>?

我最近一次因模拟而绊倒的灾难并不是因为我需要将结果实际推送到 IEnumerable<T> 的模拟对象中。

这是一个示例(仅演示 IEnumerable<T>,实际上并不是很好的基于交互的测试!):

using System;
using System.Collections.Generic;
using Rhino.Mocks;
using MbUnit.Framework;

[TestFixture]
public class ZooTest
{
    [Test]
    public void ZooCagesAnimals()
    {
        MockRepository mockery = new MockRepository();

        IZoo zoo = new Zoo();

        // This is the part that feels wrong to create.
        IList<IAnimal> mockResults = mockery.DynamicMock<IList<IAnimal>>();
        IAnimal mockLion = mockery.DynamicMock<IAnimal>();
        IAnimal mockRhino = mockery.DynamicMock<IAnimal>();

        using (mockery.Record())
        {
            Expect.Call(zoo.Animals)
                .Return(mockResults)
                .Repeat.Once();
        }

        using (mockery.Playback())
        {
            zoo.CageThe(mockLion);
            zoo.CageThe(mockRhino);

            Assert.AreEqual(mockResults, new List<IAnimal>(zoo.Animals));
        }       
    }
}


public class Zoo : IZoo
{
    private IList<IAnimal> animals = new List<IAnimal>();

    public void CageThe(IAnimal animal)
    {
        animals.Add(animal);
    }

    public IEnumerable<IAnimal> Animals
    {
        get
        {
            foreach(IAnimal animal in animals)
            {
                yield return animal;
            }
        }
    }
}

public interface IAnimal
{
}

public interface IZoo
{
    IEnumerable<IAnimal> Animals { get;}
    void CageThe(IAnimal animal);
}

我不喜欢它的工作原理,原因如下:

  • IEnumerable<IAnimal> 结果用于 IList<IAnimal> - 因为我知道这会将结果放在堆上进行检查。
  • 设置结果的内容 - 我也理解;但我的主要观点是测试 Zoo.Animals 是否返回 IEnumerable<IAnimal>,甚至更好的是,它在内部使用 yield return< /li>

有什么建议可以做得更好或更简单吗?

编辑:我正在尝试确定测试 IEnumerable<T> 和我正在使用的任何东西之间交互的最佳方式。我不是要测试 Zoo 是否可以容纳动物,而是 Zoo 暴露为 IEnumerable<IAnimal>,而 yield return > 也越来越习惯了。

请先 登录 后评论

2 个回答

Rytmis

我不太明白您希望如何在一个不是模拟的对象上设置模拟期望。此外,您正在设置返回 IList 的期望,这并不是编译器生成迭代器时真正发生的情况。

如果你想专门测试迭代器,你应该

Assert.IsNotNull(zoo.Animals);

然后验证枚举器是否确实枚举了您添加到 Zoo 中的所有内容。这就是我要去那里的目的。 :)

我不确定是否可以测试是否调用了 yield return,因为 yield return 只是编译器生成的 IEnumerable 的语法糖。例如,调用

zoo.Animals.GetEnumerator();

不会执行您在枚举器中编写的任何代码。第一次发生在第一次调用 IEnumerator.MoveNext();

现在,如果您要测试具体 Zoo 和该 Zoo 所包含的 IEnumerable 之间的交互,您应该将 IEnumerable 设为 Zoo 上的一个字段,并将模拟 IEnumerable 注入该字段,而不是直接实现具体的 IEnumerable在动物园。

希望对大家有所帮助。

请先 登录 后评论
Rytmis

如果您正在测试实现,为什么首先要尝试模拟它?为什么不只是 CageThe(IAnimal) 然后检查 Animals 是否包含该 IAnimal?

我知道你在嘲笑 IAnimals,显然你还没有任何具体的动物可以玩,但为什么不把它们做成存根,因为显然你不希望它们发生任何其他事情除了被列入名单?

编辑:大致沿着这些方向的东西(未经测试,可能无法编译,可能会吃掉你的狗等):

[TestFixture]
public class ZooTest 
{
    [Test]
    public void ZooCagesAnimals()
    {
        MockRepository mockery = new MockRepository();

        IAnimal mockLion = mockery.Stub<IAnimal>();
        IAnimal mockRhino = mockery.Stub<IAnimal>();

        IZoo zoo = new Zoo();

        zoo.CageThe(mockLion);
        zoo.CageThe(mockRhino);

        List<IAnimal> animals = new List<IAnimal>(zoo.Animals);
        Assert.IsTrue(animals.Contains(mockLion));
        Assert.IsTrue(animals.Contains(mockRhino));
    }
}
请先 登录 后评论