我们如何正确模拟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
。
有什么建议可以做得更好或者更简单吗?
编辑:我正在尝试确定最佳的方法来测试IEnumerable <T>与我所使用的东西之间的交互。我不是要测试Zoo
可以容纳动物,而是要Zoo
暴露为IEnumerable <IAnimal>
,并且也要使用yield return
。
解决方案
回答
如果要测试实现,为什么首先要模拟它?为什么不只是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)); } }
回答
我不太明白我们希望如何在不是模拟对象的对象上设置模拟期望。另外,我们还设置了返回IList的期望,当编译器生成迭代器时,这实际上不会发生。
如果要专门测试迭代器,则可能应该
Assert.IsNotNull(zoo.Animals);
然后验证枚举器实际上枚举了我们添加到Zoo中的所有东西。这就是我要去的地方。 :)
我不确定是否可以测试是否调用yield return,因为yield return只是编译器生成的IEnumerable的语法糖。例如,打电话
zoo.Animals.GetEnumerator();
不会执行我们在枚举器中编写的任何代码。第一次发生是在第一次调用IEnumerator.MoveNext();时。
现在,如果我们要测试具体的Zoo和该Zoo包含的IEnumerable之间的交互,则应将IEnumerable设置为Zoo上的一个字段,并将模拟IEnumerable注入该字段,而不是直接在Zoo中实现一个具体的IEnumerable。
我希望这会有所帮助。