Есть тут люди, пишущие программы в стиле TTD? Подскажите,
какими программами удобнее всего генерировать Mock Objects?
--
С уважением, Дмитрий.
A language that doesn't affect the way you think about programming,
is not worth knowing. -- Alan J. Perlis
> Есть тут люди, пишущие программы в стиле TTD? Подскажите,
> какими программами удобнее всего генерировать Mock Objects?
Я, как бы, соавтор и мэйнтейнер MockCreator: http://mockcreator.sf.net
Им и пользуюсь. Он статический, то есть генерирует код. Из динамических
понравился EasyMock.
--
dozen
http://dozen.ru
25 Фев 04 11:26, Dmitry Olyenyov писал(ла) All:
DO> Есть тут люди, пишущие программы в стиле TTD? Подскажите,
DO> какими программами удобнее всего генерировать Mock Objects?
да чем угодно что умеет делать stub-implement
together'om например :)
Alex Cvetkov
> да чем угодно что умеет делать stub-implement
> together'om например :)
Одного стаба достаточно только в простейших случаях.
--
dozen
http://dozen.ru
Супер! :) Буду пробовать MockCreator:)
Я пока пытаюсь начать использовать TTD, можешь подсказать
примеры хороших тестов для разного рода программ? К примеру,
как правильно писать тесты для приложений, которые работают
с базой данных напрямую, или через Hibernate. А то пока все тесты,
которые я видел либо просто примеры как писать тесты, либо
тесты чего-нибудь простенького.
Или вот, к примеру, не могу сообразить как можно
протестировать JAI.
--
С уважением, Дмитрий.
> Супер! :) Буду пробовать MockCreator:)
Mmm... ну, спрашивай, если что.
> Я пока пытаюсь начать использовать TTD, можешь подсказать
> примеры хороших тестов для разного рода программ? К примеру,
> как правильно писать тесты для приложений, которые работают
> с базой данных напрямую, или через Hibernate.
Во-первых, объекты, которые непосредственно ходят к базе,
тестируют в рамках не юнит-тестинга, а интеграционного тестинга.
То есть -- для них готовят реальные данные в базе, они реально
идут к базе, реально ею шуршат, и реально что-то возвращают.
Это всё небыстро.
А основная идея Mocks -- сделать тестирование настолько быстрым, чтобы
тесты можно было прогонять буквально после каждой вбитой строчки. Несколько
сотен мок-тестов проходят за пару секунд. Для этого приложение, работающее
с базой данных, нужно аккуратно разрезать как раз по линии
бизнес-код -- persistence code. Классы, отвечающие за хождение к базе,
заменить моками. Написать тесты на бизнес-логику, где вместо реальных
персистенс-объектов используются моки.
Более простой подход -- не париться с моками вообще, а писать много
интеграционных тестов. Но надо быть готовым к тому, что сотня тестов
может занять несколько минут, и периодически куча тестов будет обламываться
не из-за проблем в коде, а из-за проблем в базе.
> Или вот, к примеру, не могу сообразить как можно
> протестировать JAI.
Это кто такое?
--
dozen
http://dozen.ru
> А основная идея Mocks -- сделать тестирование настолько быстрым, чтобы
Спасиб!
>
> > Или вот, к примеру, не могу сообразить как можно
> > протестировать JAI.
>
> Это кто такое?
Это Java Advanced Imaging. Сейчас я его использую для того,
чтобы на картинке рисовать некоторый текст.
> А основная идея Mocks -- сделать тестирование настолько быстрым, чтобы
> тесты можно было прогонять буквально после каждой вбитой строчки. Несколько
> сотен мок-тестов проходят за пару секунд. Для этого приложение, работающее
> с базой данных, нужно аккуратно разрезать как раз по линии
> бизнес-код -- persistence code. Классы, отвечающие за хождение к базе,
> заменить моками. Написать тесты на бизнес-логику, где вместо реальных
> персистенс-объектов используются моки.
О! Для Hibernate, наверное можно просто создать mock для
Session. И в его mockSession.load создавать объекты. Сейчас попробую!
> О! Для Hibernate, наверное можно просто создать mock для
> Session. И в его mockSession.load создавать объекты. Сейчас попробую!
Да, что-то в этом духе. У меня было чуть по-другому -- была своя
обертка над hibernate.Session, которую я уже мокал. Сами объекты
типа User к базе не ходили, поэтому для них моков было не нужно,
только для самого Session.
--
dozen
http://dozen.ru
26 Feb 04 08:01, Dmitry Olyenyov wrote to Vladimir Dozen:
>> А основная идея Mocks -- сделать тестирование настолько быстрым, чтобы
>> > Или вот, к примеру, не могу сообразить как можно
>> > протестировать JAI.
>> Это кто такое?
DO> Это Java Advanced Imaging. Сейчас я его использую для того,
DO> чтобы на картинке рисовать некоторый текст.
В одном проекте (навароченая система отчетов) делали некое подобие унит тестов
для того что видно на разпечатке ну и создали по тесту на каждый возможный
елемент. Был тестовый репорт и эталонный pdf файл. Репорт выполнялся, тестовые
и эталонные файлы с помощю gostscroptа рендерились в битмап и поточечно
срсвнивались. Если несовподение то тест проваливался. Все это засунули под
CruiseControl которий все время смотрел в CVS репозиторий. Вместо базы данных
выло hsqldb. Тестов выло примерно сорок. Результат был поразителный!
Ansis
> В одном проекте (навароченая система отчетов) делали некое
> подобие унит тестов для того что видно на разпечатке ну и
> создали по тесту на каждый возможный елемент. Был тестовый
> репорт и эталонный pdf файл. Репорт выполнялся, тестовые и
> эталонные файлы с помощю gostscroptа рендерились в битмап
> и поточечно срсвнивались. Если несовподение то тест
> проваливался. Все это засунули под CruiseControl которий
> все время смотрел в CVS репозиторий. Вместо базы данных
> выло hsqldb. Тестов выло примерно сорок. Результат был
> поразителный!
Ага, у меня примерно такая же идея и была.
> Или вот, к примеру, не могу сообразить как можно
> протестировать JAI.
Типовое заблуждение - что юнит-тестами можно тестировать все что угодно.
Ты уверен, что это тот случай, когда можно и нужно?
Не, я как раз таки сомневаюсь, что такое можно нормально
протестировать...
А вообще, какие еще существуют методики, кроме Test-driven
design? И если не очень сложно, накидайте ссылок где
поподробнее расписываются эти методики (rus/eng - без
разницы)?
А чем Вы сами пользуетесь, к чему больше склонны?
--
С уважением, Дмитрий.
26 Фев 04 05:25, Vladimir Dozen писал(ла) Alex Cvetkov:
>> да чем угодно что умеет делать stub-implement
>> together'om например :)
VD> Одного стаба достаточно только в простейших случаях.
это уже интересней. а что за более сложные случаи. можно примеры или ссылки.
в книге Test-driven ddevelopment(design уже точно названия не помню) про эти
mock object почемуто очень немного
Alex Cvetkov
> это уже интересней. а что за более сложные случаи. можно примеры или ссылки.
Mock -- это заглушка, которую можно настраивать. Простой стаб на вызов
getName(), например, будет всегда возвращать null. Или "vasya". Mock
конфигурируется на возврат именно того, что необходимо в данном тесте:
mock.expectGetName(null); // return null on first call
mock.expectSetName("vasya");
mock.expectGetName("vasya"); // return "vasya"
// дальнейшие вызовы идут где-то в глубинах бизнес-логики
mock.getName();
mock.setName("vasya");
mock.getName();
И это только простейшая, жеская конфигурация. Вот что умеет MockCreator:
1. dummy. Засетапить метод, чтобы он всегда возращал одно и то же значение
или кидал одно и тоже исключение (для одного набора параметров):
setGetAgeDummy("vasya",22); // getAge("vasya") вернет 22
setGetAgeDummy("petya",33); // guess what happens here
setGetAgeDummy("masha",new GetOutException()); // маша не любит говорить о возрасте
setGetAgeDummy(new UnknownAgeException()); // для остальных возраст неизвестен
2. expect. Ожидать вызов метода с заданными параметрами, и вернуть заданное значение
или кинуть заданное исключение. От dummy отличается тем, что важен порядок вызовов.
Вызов незасетапленного метода кинет исключение. Вызов засетапленного метода не в
свой turn -- тоже.
expectGetAge("vasya",22); // два раза уже не позовешь.
3. accept. Очень похож на expect, но каждый параметр не обязан иметь точно такое
же значение, как засетаплено. Вместо этого, сетапятся специальные компараторы,
которые проверяют, находится ли значение в заданном диапазоне.
// код, который надо протестировать (а именно, убедиться, что при переданном
// null, автоматом подставляется текущая дата
this.realLog(now==null?new Date():now,severity,message);
// проверка -- переданная дата отличается от текущей не более, чем на секунду
mock.acceptRealLog(new ExpectDate(new Date(),1000),new Integer(5),"Hello");
mock.log(null,5,"Hello");
4. expectCtor. ожидание создания объекта. все, как про метод, только ожидаем,
что бизнес-код вызовет конструктор объекта с заданными параметрами. Получивщийся
объект тоже можно сетапить (хотя он еще не существует).
5. блоки. набор функций, которые могут быть вызваны в любой последовательности,
но только все вместе. Например, надо проверить, что код проверяет права:
validator.checkRight(USER_READ_PROFILE);
validator.checkRight(USER_READ_HISTORY);
код теста:
mock.startBlock();
mock.expectCheckRight(USER_READ_PROFILE);
mock.expectCheckRight(USER_READ_HISTORY);
mock.endBlock();
...
Ну, и еще кучку всего.
> в книге Test-driven ddevelopment(design уже точно названия не помню) про эти
> mock object почемуто очень немного
Книга эта -- вообще очень базовая. Там наиболее ценен пример
разработки функции расчета чисел Фибоначчи (или факториала? не помню),
потому что он дает представление об образе мышления. Моки -- это несколько
более продвинутая технология, нужная при очень большом числе тестов (от пары
сотен и более) и желании гонять тесты очень часто.
--
dozen
http://dozen.ru
Попробовал поизучать MockCreator, написал простой класс и по нему сгенерился
мок класс:
public class User {
protected long id;
protected String name;
public User() {}
public User(final long id, final String name) {
setId(id);
setName(name);
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Сделал фабрику:
public class UserHome {
public static boolean IS_TEST = false;
public static User getUser() {
if(IS_TEST) {
try {
Class cl = Class.forName("com.dark.db.mock.MockUser");
Constructor constructor = cl.getConstructor(new Class[]{Long.TYPE,
String.class});
return (User) constructor.newInstance(new Object[]{new Long(0),
""});
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException(e);
} catch (InstantiationException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
} else {
return new User(0, "");
}
}
}
При попытке создать мокс объект, в этом методе вылетает эксепшен:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at
sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAcces
sorImpl.java:39)
at
sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstruc
torAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:274)
...........
..........
Caused by: de.abstrakt.mock.MockException:
unexpected call to com.dark.test.MockUser#null#setId(long)
with params [0]
while expected []
at de.abstrakt.mock.MockCore.makeException(MockCore.java:386)
at de.abstrakt.mock.MockCore.unexpectedCall(MockCore.java:378)
at de.abstrakt.mock.MockCore.getReturnValue(MockCore.java:210)
at com.dark.test.MockUser.setId(MockUser.java:261)
at com.dark.User.<init>(User.java:20)
at com.dark.test.MockUser.<init>(MockUser.java:17)
public void setId(long p0_long) {
MockCore.enteredMethodBody();
Object returnValue = null;
if (MockCore.isDelegate(getClassObjectMethodSignature("setId(long)"))) {
super.setId(p0_long);
return;
}
java.util.List params = new java.util.ArrayList();
params.add(new Long(p0_long));
returnValue =
MockCore.getReturnValue(getClassObjectMethodSignature("setId(long)"),
params); // <----- Вот здесь Эксепшен
if (returnValue instanceof RuntimeException) {
throw (RuntimeException) returnValue;
}
if (returnValue instanceof Error) {
throw (Error) returnValue;
}
}
With best regards, Max_Grigoriev.
--
Отправлено через сервер Форумы@mail.ru - http://talk.mail.ru
02 Мар 04 20:05, Vladimir Dozen писал(ла) Alex Cvetkov:
>> это уже интересней. а что за более сложные случаи. можно примеры или
>> ссылки.
VD> Mock -- это заглушка, которую можно настраивать. Простой стаб на
VD> вызов
VD> getName(), например, будет всегда возвращать null. Или "vasya".
VD> Mock
VD> конфигурируется на возврат именно того, что необходимо в данном
VD> тесте:
помоему это только усложнит тесты. не вижу особого смысла в переиспользовании
Mock'ов такой ценой. повышаеться вероятность поиметь ошибку в тесте.
хотя для ленивых наверное в самый раз :)
Alex Cvetkov
> Попробовал поизучать MockCreator, написал простой класс и по нему сгенерился
> мок класс:
unexpected call to com.dark.test.MockUser#null#setId(long)
Это означает, что setId не был засетаплен. Последовательность
событий -- ты создал Mock; Mock, как subclass User, создал
User; тот вызвал setId, и мы получили исключение.
Проблема в Mock'анье _классов_ (не интерфейсов). Это просто
работает не всегда, потому что Mock _обязан_ позвать какой-то
super-конструктор. Удовлетворительно решения в общем случае нет,
но в твоем случае достаточно в конструкторе звать не setId(),
а просто присваивать
this.id = id;
Если это по каким-то причинам требуется, то нужно выделить
интерфейс IUser, и мокать его, а User implements IUser.
--
dozen
http://dozen.ru
> помоему это только усложнит тесты.
А по-моему, ты просто не понимаешь назначения моков.
Суть юнит-тестов -- протестировать один модуль в _отрыве_ от других.
Это не всегда возможно из-за тесных связей между модулями.
Моки позволяют оторвать модуль от внешних связей.
При этом скорость тестирования возрастает на порядки, а
сложность -- _снижается_, потому, например, что тебе на надо
сетапить базу для прогона простого теста на проверку прав.
Или, например, тесты получения данных от внешнего источника --
что, каждый раз ждать, пока там удаленный сервер квоту отдаст?
Я уже приводил пример реального проекта -- тесты без моков
занимают 10 минут, тесты с моками -- несколько секунд. При этом
имеет место лавинообразный эффект -- при использовании моков
девелоперы начинают гонять тесты буквально после каждого изменения
кода (несколько секунд никого не напрягают), в результате все ошибки
находятся моментально, и стоимость исправления их -- минимальна (сравни
со случаем, когда тесты гоняются ночью, и утром девелопер обнаруживает,
что задача, которую он "закончил" вчера, поломала кучу мест).
Разумеется, при использовании моков нужны еще и регрессионные тесты,
которые как раз и идут долго, и гоняются ночью. Но они гораздо менее
подробные, и проверяют функциональность в целом.
> не вижу особого смысла в переиспользовании
> Mock'ов такой ценой. повышаеться вероятность поиметь ошибку в тесте.
Вероятность как раз снижается. В обычном тесте
у тебя может случиться сбой из-за наводок от внешних
модулей (особенно от базы данных).
> хотя для ленивых наверное в самый раз :)
Ленивые вообще тестированием не озабочены.
--
dozen
http://dozen.ru
03 Мар 04 20:13, Vladimir Dozen писал(ла) Alex Cvetkov:
>> помоему это только усложнит тесты.
VD> А по-моему, ты просто не понимаешь назначения моков.
Да я собственно не про моки говорил а про то что генерит твой генератор.
помоему проще написать мок руками самому, чем работать со столь сложным
обьектом каковой получаеться у тебя.
Alex Cvetkov
> Да я собственно не про моки говорил а про то что генерит твой генератор.
> помоему проще написать мок руками самому, чем работать со столь сложным
> обьектом каковой получаеться у тебя.
Придумай более простой способ продекларировать объекту поведение,
которое от него требуется в тесте, и будет всем счастье, и будут
пользоваться твоим mock-генератором. А пока все API mock-generator'ов
в той или иной степени неудобны. MockCreator'ом пользуется куча контор,
и пожелания, которые я от них слышу -- это не упростить API, а, наоборот,
добавить наворотов, потому что "случаи -- они бывают разные".
Да и, на самом деле, все проще, чем кажется. Например, тест: убедиться,
что при невозможности получить коннекш от базы данных pool вначале
попробует еще раз, а потом сможет вернуть полученный Connection:
// тест упрощен для наглядности
public void testPauseAtPeakLoad() throws Exception
{
MockDataSource mds = new MockDataSource();
Pool pool = new Pool(mds);
mds.expectGetConnection(new SQLException());
mds.expectGetConnection(new MockConnection());
assertNotNull(pool.getConnection());
}
Попробуй этот сценарий протестировать без моков? Не создавать же
руками коннекшены, пока они не кончатся...
Мне тоже поначалу сложно казалось, я шарахался от моков, как от ладана.
Потом, когда приперло, пришлось заняться, и понял, что иногда без них
гораздо хуже, чем с ними. Зависит от проекта.
--
dozen
http://dozen.ru
??>> Попробовал поизучать MockCreator, написал простой класс и по нему
??>> сгенерился мок класс:
VD> unexpected call to com.dark.test.MockUser#null#setId(long)
VD> Это означает, что setId не был засетаплен. Последовательность
VD> событий -- ты создал Mock; Mock, как subclass User, создал
VD> User; тот вызвал setId, и мы получили исключение.
Не совсем понял причину по которой происходит исключение.
VD> Проблема в Mock'анье _классов_ (не интерфейсов). Это просто
VD> работает не всегда, потому что Mock _обязан_ позвать какой-то
VD> super-конструктор. Удовлетворительно решения в общем случае нет,
Опять же в чем проблема звать супер конструктор ?
VD> но в твоем случае достаточно в конструкторе звать не setId(),
VD> а просто присваивать
VD> this.id = id;
Я привык пользоваться сеттерами, потом проблем меньше возникает при каком
либо изменении.
VD> Если это по каким-то причинам требуется, то нужно выделить
VD> интерфейс IUser, и мокать его, а User implements IUser.
Да но если у меня в Юзере есть еще логика, то как быть?
Или если у меня есть два вида пользователей Anonymous и Admin, и все они от
IUser , а надо промокать оба их.
> Не совсем понял причину по которой происходит исключение.
...
> Опять же в чем проблема звать супер конструктор ?
Mock создается как наследник класса (он же должен быть
взаимозаменяем с ним по типу, то есть другого выхода нет).
При вызове конструктора MockXXX() этот конструктор обязан
вызвать какой-то суперконструктор XXX(). Если в XXX() есть
вызов setA(), то это будет уже вызов MockXXX().setA().
MockXXX().setA() бросает исключение, если он не был засетаплен
как ожидаемый. Он не был. Он кинул исключение.
Эта проблема общая для всех Mock generators. Точнее, большинство
просто отказывается генерить моки для классов, ограничиваясь только
интерфейсами.
> Я привык пользоваться сеттерами, потом проблем меньше возникает при каком
> либо изменении.
Reasonable. Тогда иного выхода, как выделить интерфейс, нет.
Мгм... на самом деле, есть. Кривоватый. Родить в User protected
constructor без параметров. Насколько я помню ;) он является
предпочтительным для MockCreator.
protected User(){}; // design smell
> Да но если у меня в Юзере есть еще логика, то как быть?
Если ты мокаешь User, то его логика тебе в тесте не нужна.
Мокают типы, которые в тесте _не_ тестируют.
Тестируют типы, которые _используют_ моки.
Моки -- это заглушки для того, что тебе в тесте неинтересно, но
необходимо.
> Или если у меня есть два вида пользователей Anonymous и Admin, и все они от
> IUser , а надо промокать оба их.
Мокаешь IUser, сетапишь их так, чтобы в одном тесте он вел себя как админ,
в другом -- как анонимус.
--
dozen
http://dozen.ru
> Попробовал поизучать MockCreator, написал простой класс и по нему сгенерился
> мок класс:
>
> public class User {
> public User(final long id, final String name) {
> setId(id);
> setName(name);
> }
> ...
> }
> При попытке создать мокс объект, в этом методе вылетает эксепшен:
> unexpected call to com.dark.test.MockUser#null#setId(long)
Вышли на меня на неделе какие-то голландцы, что ли... с такой же
проблемой. Плюс мои старые работодатели вышли, тоже хотят конструкторы
с сеттерами мокать. Ну, раз накопилась критическая масса, то вот вам:
вышла новая версия 2.4.1, где сетапить мок можно до его создания:
MockUser.PreMock pre = new MockUser.PreMock();
pre.expectSetId(5);
pre.expectSetName("Vasya");
pre.expectCtor(5,"Vasya");
MockUser mock = new MockUser(5,"Vasya");
Фишка в чем -- разделены конфигурация и создание. Соответственно,
superctor не зовется до конца конфигурации, и ничего не портит.
Разумеется, старый способ конфигурения -- через сам мок -- тоже
работает, он не старый, а основной, а этот -- альтернативный, для
сложных случаев.
P.S. Там еще есть изменений, типа expectAnyCtor() и acceptFooDummy(),
на странице download'а описано вкратце.
--
dozen
http://dozen.ru
VD> вышла новая версия 2.4.1, где сетапить мок можно до его создания:
VD> MockUser.PreMock pre = new MockUser.PreMock();
VD> pre.expectSetId(5);
VD> pre.expectSetName("Vasya");
VD> pre.expectCtor(5,"Vasya");
VD> MockUser mock = new MockUser(5,"Vasya");
Вот спасибо :)
>
> > Супер! :) Буду пробовать MockCreator:)
>
> Mmm... ну, спрашивай, если что.
Только теперь окончательно созрел для unit тестов:)
Появился вопрос про MockCreator:)
Проблема такая - сейчас пишу тесты
для существующего работающего кода. SessionBean + Hibernate. Для
Hibernate Session, SessionFactory сделал моки, и теперь пытаюсь
угадать в каком порядке надо вызывать MockSession ms; ms.expectXXXX :)
Теперь сам вопрос: Можно ли сделать так, чтобы Mock, если я вызываю не
заexpect'ченый метод, говорил, какой именно метод должен быть вызван
вместо этого?
Вижу, что объяснение немного путанное, так что вот пример:
public void testCreateDuplicatedSubscriber() throws Exception {
MockSession ms = new MockSession();
final Subscriber ss = new Subscriber("de...@alb.kz", 26, true, 10000);
// ms.expectGet(Subscriber.class, ss.getEmail(), ss);
ms.expectClose((Connection) null);
msf.setOpenSessionDummy(ms);
final SubscriptionManagement subscriptionManagement = SubscriptionManagementUtil.getHome().create();
boolean accountExists = false;
try {
subscriptionManagement.createSubscriber(ss, new Long(1), new Long(1), new Long(1), new Long(1));
} catch (AccountAlreadyExistsException e) {
accountExists = true;
}
assertTrue("Trying to create duplicated account", accountExists);
ms.verify();
msf.verify();
}
При закоментареном expectGet(...) Выводит такую ошибку:
System Error; nested exception is: de.abstrakt.mock.MockException:
unexpected call to net.sf.hibernate.MockSession#7#close() with params
[]
хотя вроде бы должен выдавать unexpected call to
MockSession.get... Или я чего-то недопонимаю?..
--
С уважением, Дмитрий.
Каждое яблоко мечтает стать яблоком познания или хотя-бы яблоком раздора