Фреймворк для удобного создания мок-объектов для тестирования Rhino Mocks очень быстро развивается. Приходится довольно часто перечитывать документацию чтобы быть "в теме".
Сегодня как раз был такой день Залез на сайт и был несказанно рад тому, что есть Quick Refference 3.3 (всего на 3 странички) для быстрого вхождения (жаль что для версии 3,3 когда уже есть 3.5...).
Для тех кто совсем не пользовался RhinoMocks можно посмотреть Quick Refference 3.1 (всего 1 страничка, самое главное).
Мне кажется, record-replay уже морально устарел: на дворе век Arrange-Act-Assert. По крайней мере, я сейчас потихоньку переключаюсь на это дело, и тесты становятся значительно чище и понятнее.
По поводу синтаксиса можно посмотреь блог ayende и группу google про rhino mocks.
ulu
Здравствуйте, Aikin, Вы писали:
A>Фреймворк для удобного создания мок-объектов для тестирования Rhino Mocks очень быстро развивается. Приходится довольно часто перечитывать документацию чтобы быть "в теме".
A>Сегодня как раз был такой день Залез на сайт и был несказанно рад тому, что есть Quick Refference 3.3 (всего на 3 странички) для быстрого вхождения (жаль что для версии 3,3 когда уже есть 3.5...). A>Для тех кто совсем не пользовался RhinoMocks можно посмотреть Quick Refference 3.1 (всего 1 страничка, самое главное).
A>Вот цитаты: A>Создание мока: A>
A>IConnection connection = mocks.CreateMock<IConnection>();
A>
A>Использование using для разделения "зоны записи мока" от "зоны использования мока": A>
A>using (mocks.Record())
A>{
A>... Expect.Call(...);
A>}
A>using (mocks.Playback())
A>{
A> ...
A>}
A>
A>Примеры установки ожиданий: A>
A>Expect.Call(list.Contains(42)).Return(true);
A>Expect.Call(dict.TryGetValue("key", out outParam)).OutRef("value").Return(true);
A>
A>Запись ограничений на параметры: A>
A>Expect.Call(file.Read(null, 0, 0))
A> .Constraints(Property.Value("Length", 4096), Is.Equal(0), Is.GreaterThan(0) && Is.LessThan(4096));
A>
Здравствуйте, ulu, Вы писали:
ulu>Мне кажется, record-replay уже морально устарел: на дворе век Arrange-Act-Assert. По крайней мере, я сейчас потихоньку переключаюсь на это дело, и тесты становятся значительно чище и понятнее.
что-то я не понял смысл Arrange-Act-Assert, можно поподробнее обьяснить, по примерам выходит тоже самое, что и в Moq.
Здравствуйте, cadet354, Вы писали:
C>Здравствуйте, ulu, Вы писали:
ulu>>Мне кажется, record-replay уже морально устарел: на дворе век Arrange-Act-Assert. По крайней мере, я сейчас потихоньку переключаюсь на это дело, и тесты становятся значительно чище и понятнее.
C>что-то я не понял смысл Arrange-Act-Assert, можно поподробнее обьяснить, по примерам выходит тоже самое, что и в Moq.
Наверное, похоже на Moq, я с ним не знаком.
Вот написал тут немного про свое видение вопроса здесь.
спасибо, я так примерно и делал,( а какой был подход до этого), оказывается следовал веяниям моды ,
хотя вот этого:
Как все это организовать, чтобы было понятно? Начнем с последнего. Очень важный принцип, который я очень долго игнорировал (потому, что "все и так работало") -- надо в каждом тестовом методе иметь ровно один Assert. Более того, кроме него, в сущности, ничего там не должно быть. Все остальное безжалостно тащим в Сетап.
А что, если какой-то тестовый метод требует другого сетапа? Верный признак того, что он сидит не в своем тестовом классе. И тут включается другое правило -- На каждый тестовый класс должен быть один общий Act. То есть, мы каждый раз в Сетапе производим тестируемое действие, а потом в тестовых методах проверяем его эффекты. Это абсолютно противоположно новичковому методу, в котором тестовый метод содержит подготовку контекста, действие, а потом проверку многочисленных результатов.
у меня не было, действительно в методах проверка многочисленных результатов, но не приведет ли предлагаемый метод к лавинообразному увеличению количества тестов на ровном месте?
Здравствуйте, cadet354, Вы писали:
C>Здравствуйте, ulu, Вы писали:
C>спасибо, я так примерно и делал,( а какой был подход до этого), оказывается следовал веяниям моды , C>хотя вот этого: C>
C>Как все это организовать, чтобы было понятно? Начнем с последнего. Очень важный принцип, который я очень долго игнорировал (потому, что "все и так работало") -- надо в каждом тестовом методе иметь ровно один Assert. Более того, кроме него, в сущности, ничего там не должно быть. Все остальное безжалостно тащим в Сетап.
C>А что, если какой-то тестовый метод требует другого сетапа? Верный признак того, что он сидит не в своем тестовом классе. И тут включается другое правило -- На каждый тестовый класс должен быть один общий Act. То есть, мы каждый раз в Сетапе производим тестируемое действие, а потом в тестовых методах проверяем его эффекты. Это абсолютно противоположно новичковому методу, в котором тестовый метод содержит подготовку контекста, действие, а потом проверку многочисленных результатов.
C>у меня не было, действительно в методах проверка многочисленных результатов, но не приведет ли предлагаемый метод к лавинообразному увеличению количества тестов на ровном месте?
В том-то и дело, что здесь место неровное. Юнит тест -- он не потому юнит, что проверяет функциональность какого-то одного небольшого куска системы, а потому, что он проверяет небольшой кусок функциональности. В идеале, посмотрев на название теста (и заменив подчеркивания на пробелы), можно получить кусочек ТЗ (и отрапортовать клиенту о его готовности):
[TestFixture] public class Если_нажать_на_красную_кнопку {
[Setup] public void Жмем_кнопку() {...}
[Test] public void Появится_сообщение() {...}
[Test] public void Текст_появившегося_сообщения__больше_не_жми() {...}
}
"Лавинообразное увеличение количества тестов" -- на самом деле, количество ассертов сохраняется, просто они разбегаются по разным методам.
Есть тут другая опасность -- когда ассерты взаимозависимы. Например, мы изготавливаем коллекцию, потом проверяем, что у нее только один элемент, а потом начинаем проверять свойства этого элемента. Если что-то сломалось, и в коллекции стало ноль элементов, то все ассерты сломаются, и мы увидим кучу красных кружочков, хотя на самом деле сломался только один тест. Это называется "эффектом домино". Выход есть: в MbUnit, например, можно задать явную зависимость тестов: если один не сработал, остальные не запускаются.
Допустим тестриуемый метода должен запросить два числа из разных источников и выдать их сумму. Мне нужно сделать три теста (запросил число из одного источника, запросил из другого, вернул сумму)? Чем это будет лучше одного теста? Чем хуже понятно, нам надо писать три теста вместо одного и 3 раза вызывать тестируемый метод.
Здравствуйте, Ziaw, Вы писали:
Z>Что вообще дает такой подход? Какие плюсы?
Модульнось. SRP. Распределением ответственности.
Z>Допустим тестриуемый метода должен запросить два числа из разных источников и выдать их сумму. Z>Мне нужно сделать три теста (запросил число из одного источника, запросил из другого, вернул сумму)?
Правильно, так и будет. Z>Чем это будет лучше одного теста? Чем хуже понятно, нам надо писать три теста вместо одного и 3 раза вызывать тестируемый метод.
А почему тест который проверяет сумму двух чисел должен заморачиваться на то откуда они взялись?
И скажи, вот твой тест упал. Что именно пошло не так? Получение первого числа, второго, или сумматор глюканул?
Здравствуйте, Aikin, Вы писали:
Z>>Что вообще дает такой подход? Какие плюсы? A>Модульнось. SRP. Распределением ответственности.
А без лозунгов?
Z>>Чем это будет лучше одного теста? Чем хуже понятно, нам надо писать три теста вместо одного и 3 раза вызывать тестируемый метод. A>А почему тест который проверяет сумму двух чисел должен заморачиваться на то откуда они взялись?
Тест проверяет то, что должен сделать метод. Метод должен сделать 3 вещи:
Взять из одного сервиса число a. Взять из другого число b. Сложить их и вернуть результат.
public interface INumberSource
{
int GetNumber();
}
public class SomeService
{
private readonly INumberSource aSource;
private readonly INumberSource bSource;
public SomeService(INumberSource aSource, INumberSource bSource)
{
this.aSource = aSource;
this.bSource = bSource;
}
public int SomeMethod()
{
return aSource.GetNumber() + bSource.GetNumber();
}
}
[TestFixture]
public class SomeServiceTests
{
MockRepository repos;
INumberSource aSrcMock;
INumberSource bSrcMock;
SomeService service;
[SetUp]
public void Setup()
{
repos = new MockRepository();
aSrcMock = repos.CreateMock<INumberSource>();
bSrcMock = repos.CreateMock<INumberSource>();
service = new SomeService(aSrcMock, bSrcMock);
}
[Test]
public void SomeMethodTest()
{
using (repos.Record())
{
Expect.Call(aSrcMock.GetNumber()).Return(1);
Expect.Call(bSrcMock.GetNumber()).Return(2);
}
using (repos.Playback())
{
var result = service.SomeMethod();
Assert.AreEqual(3, result);
}
}
}
Как будут выглядеть 3 теста? Чем они удобнее одного?
A>И скажи, вот твой тест упал. Что именно пошло не так? Получение первого числа, второго, или сумматор глюканул?
Тест при падении говорит, какой ассерт не сработал. Этого достаточно.
Здравствуйте, Aikin, Вы писали:
A>Здравствуйте, Ziaw, Вы писали:
Z>>Что вообще дает такой подход? Какие плюсы? A>Модульнось. SRP. Распределением ответственности.
Z>>Допустим тестриуемый метода должен запросить два числа из разных источников и выдать их сумму. Z>>Мне нужно сделать три теста (запросил число из одного источника, запросил из другого, вернул сумму)? A>Правильно, так и будет. Z>>Чем это будет лучше одного теста? Чем хуже понятно, нам надо писать три теста вместо одного и 3 раза вызывать тестируемый метод. A>А почему тест который проверяет сумму двух чисел должен заморачиваться на то откуда они взялись?
A>И скажи, вот твой тест упал. Что именно пошло не так? Получение первого числа, второго, или сумматор глюканул?
A>СУВ, Aikin
+1.
Если не хочется вызывать метод 3 раза, то можно отправить его в FixtureSetup. Но там лучше держать Arrange.
.
ulu>Если не хочется вызывать метод 3 раза, то можно отправить его в FixtureSetup. Но там лучше держать Arrange.
Т.е. на каждый метод тестируемого класса мы делаем свой TestFixture? Не слишком ли жирно? Плюсы подхода должны быть просто громадными, чтобы оправдать такое.
Начнем с того, что ulu говорит совсем про другое. Про реорганизацию тестов, а не про бессмысленное увеличение их количества.
И с того, что я тебя неправильно понял. Я считал, что тестиреумый метод сам занимается получением этих двух чисел.
Z>Тест проверяет то, что должен сделать метод. Метод должен сделать 3 вещи: Z>Взять из одного сервиса число a. Взять из другого число b. Сложить их и вернуть результат.
C чего ты взял, что ulu предлагал на разбить функциональность из 3-х шагов (1 ассерт) на три теста (3 ассерта)?
Z>Как будут выглядеть 3 теста? Чем они удобнее одного?
То что ты написал это третий тест То что "Expect.Call(...)" является еще и ассертом -- дополнительный плюс Мок-тестирования (чесно говоря, спорный плюс).
Первый тест будет проверять FirstConcreteNumberSource, а второй -- SecondConcreteNumberSource
Здравствуйте, Ziaw, Вы писали:
Z>Т.е. на каждый метод тестируемого класса мы делаем свой TestFixture?
Не на каждый метод, а на каждое состояние тестируемого объекта.
Z> Не слишком ли жирно? Плюсы подхода должны быть просто громадными, чтобы оправдать такое.
У меня тоже есть сомнения.
Здравствуйте, Aikin, Вы писали:
A>Здравствуйте, Ziaw, Вы писали:
Z>>Как будут выглядеть 3 теста? Чем они удобнее одного? A>То что ты написал это третий тест То что "Expect.Call(...)" является еще и ассертом -- дополнительный плюс Мок-тестирования (чесно говоря, спорный плюс).
Да, потому с версии 3.5 предпочтительнее является использование стабов. Ведь третий тест не должен проверять, что методы Expect.Call действительно были вызываны. И Expect.Call здесь просто информирует мокоген о том, что нужно вернуть. При использовании стабов информация о результатах используется, но факт вызова метода не проверяется.
A>Первый тест будет проверять FirstConcreteNumberSource, а второй -- SecondConcreteNumberSource
А вот это уже похоже на тестирование реализации метода. Что если SomeService не обязан вызывать эти методы в реализации метода SomeMethod? Возможно он закэшировал нужные значения в конструкторе? Т.е. проверяя факты вызова тех методов мы проверяем вовсе не контракт метода.
Здравствуйте, Aikin, Вы писали:
Z>>Т.е. на каждый метод тестируемого класса мы делаем свой TestFixture? A>Не на каждый метод, а на каждое состояние тестируемого объекта.
Состояние по сути является по сути еще одним аргументом к тестируемому методу. Мне трудно понять, чем оно отличается от других аргументов.
Здравствуйте, samius, Вы писали:
Z>>>Как будут выглядеть 3 теста? Чем они удобнее одного? A>>То что ты написал это третий тест То что "Expect.Call(...)" является еще и ассертом -- дополнительный плюс Мок-тестирования (чесно говоря, спорный плюс). S>Да, потому с версии 3.5 предпочтительнее является использование стабов. Ведь третий тест не должен проверять, что методы Expect.Call действительно были вызываны. И Expect.Call здесь просто информирует мокоген о том, что нужно вернуть. При использовании стабов информация о результатах используется, но факт вызова метода не проверяется.
Угу.
A>>Первый тест будет проверять FirstConcreteNumberSource, а второй -- SecondConcreteNumberSource S>А вот это уже похоже на тестирование реализации метода. Что если SomeService не обязан вызывать эти методы в реализации метода SomeMethod? Возможно он закэшировал нужные значения в конструкторе? Т.е. проверяя факты вызова тех методов мы проверяем вовсе не контракт метода.
Эти два теста будут находится среди тестов для FirstConcreteNumberSource и SecondConcreteNumberSource и никак не относится к SomeService
Здравствуйте, Ziaw, Вы писали:
Z>>>Т.е. на каждый метод тестируемого класса мы делаем свой TestFixture? A>>Не на каждый метод, а на каждое состояние тестируемого объекта. Z>Состояние по сути является по сути еще одним аргументом к тестируемому методу. Мне трудно понять, чем оно отличается от других аргументов.
С этим сложно спорить. Хотя это и не совсем так. Раз "парамеры" находятся в разных местах (в объекте и аргументах метода), то это кому-то надо.
Вопрос: У тебя в одном тесте проверяется вызов метода с разными значениями параметров?
Здравствуйте, Aikin, Вы писали:
A>Здравствуйте, Ziaw, Вы писали:
Z>>>>Т.е. на каждый метод тестируемого класса мы делаем свой TestFixture? A>>>Не на каждый метод, а на каждое состояние тестируемого объекта. Z>>Состояние по сути является по сути еще одним аргументом к тестируемому методу. Мне трудно понять, чем оно отличается от других аргументов. A>С этим сложно спорить. Хотя это и не совсем так. Раз "парамеры" находятся в разных местах (в объекте и аргументах метода), то это кому-то надо.
A>Вопрос: У тебя в одном тесте проверяется вызов метода с разными значениями параметров?
Вопрос не ко мне, но я свою копейку вверну:
MbUnit умеет делать параметризованные тесты, куда можно прикручивать атрибутами либо комбинации параметров, либо провайдеры параметров. Фактически тело метода одно, но тесты создаются разные — по числу комбинаций параметров либо провайдеров.
Потому да, бывает что один метод проверяет вызов метода с разными значениями, но это физически разные тесты!
Новый NUnit тоже позволяет делать тесты с параметрами, только я еще не глядел на него пока и не знаю, трактует ли он их как разные тесты, либо как один.