Подтвердить что ты не робот

Mocking метод GetEnumerator() типов IEnumerable <T>

В носорогах из носорога возникает следующий тестовый случай:

[TestFixture] 
    public class EnumeratorTest 
    { 
        [Test] 
        public void Should_be_able_to_use_enumerator_more_than_once() 
        { 
            var numbers = MockRepository.GenerateStub<INumbers>(); 
            numbers.Stub(x => x.GetEnumerator()).Return(new List<int> 
{ 1, 2, 3 }.GetEnumerator()); 
            var sut = new ObjectThatUsesEnumerator(); 
            var correctResult = sut.DoSomethingOverEnumerator2Times 
(numbers); 
            Assert.IsTrue(correctResult); 
        } 
    } 
    public class ObjectThatUsesEnumerator 
    { 
        public bool DoSomethingOverEnumerator2Times(INumbers numbers) 
        { 
            int sum1 = numbers.Sum(); // returns 6 
            int sum2 = numbers.Sum(); // returns 0 =[ 
            return sum1 + sum2 == sum1 * 2; 
        } 
    } 
    public interface INumbers : IEnumerable<int> { } 

Я думаю, что в этом тестовом случае есть что-то очень тонкое, и я думаю, что это от меня, не думая, как Rhino Mocks stubbing фактически работает. Как правило, когда вы перечислите IEnumerable, вы начинаются со свежего IEnumerator. В приведенном выше примере он выглядит как я мог бы повторно использовать тот же счетчик во второй раз, когда я вызывающая сумма, и если счетчик уже находится в конце последовательность, которая объясняет, почему второй вызов Sum() возвращает 0. Если это так, то как я мог высмеивать GetEnumerator() в таких способ, которым он ведет себя так, как я этого хочу (например, новый перечислитель или тот же счетчик reset в позицию 0)?

Как бы вы изменили вышеуказанный тестовый пример, так что второй вызов .Sum() фактически возвращает 6 вместо 0?

4b9b3361

Ответ 1

Функция WhenCalled() api позволяет динамически разрешать возвращаемые значения.

Изменение тестового примера на следующее позволит ему пройти:

numbers.Stub(x => x.GetEnumerator())
                     .Return(null)
                     .WhenCalled(x => x.ReturnValue = 
                                    new List<int> { 1, 2, 3 }.GetEnumerator()
                                 );

Поэтому вместо того, чтобы возвращать тот же счетчик, зашитое поведение всегда будет возвращать новый счетчик.

Ответ 2

Утверждение

numbers.Stub(x => x.GetEnumerator()).Return(new List<int> { 1, 2, 3 }.GetEnumerator());

совпадает с

var enumerator = new List<int> { 1, 2, 3 }.GetEnumerator();
numbers.Stub(x => x.GetEnumerator()).Return(enumerator);

В своем тесте вы рассказываете Rhino Mocks, чтобы дать идентичный IEnumerator<int> экземпляр enumerator дважды. Это не то, что вы намеревались. Один экземпляр IEnumerator<int> хорош только для одного перечисления, а не для двух перечислений (Reset() обычно не поддерживается). Вы назначили Rhino Mocks, чтобы дать два разных экземпляра IEnumerator<int>, чтобы их можно было суммировать отдельно, так же, как и любой вызов любой другой функции GetEnumerator<int>().

Ответ 3

Отказ от ответственности: я работаю в Typemock

Я не знаю, можете ли вы использовать Rhino для этого, но вы можете использовать Isolator с AAA API, который имеет именно то, что вы ищете -

[Test] 
public void Should_be_able_to_use_enumerator_more_than_once() 
{
   var numbers = Isolate.Fake.Instance<INumbers>();
   Isolate.WhenCalled(() => numbers).WillReturnCollectionValuesOf(new List<int>{ 1, 2, 3 });
   var sut = new ObjectThatUsesEnumerator(); 
   var correctResult = sut.DoSomethingOverEnumerator2Times(numbers); 
   Assert.IsTrue(correctResult); 
}

Подробнее см. Управление коллекциями в Руководстве разработчика.