Модераторы: LSD
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> JUNIT 4.x зависимости, JUNIT 4.x зависимости всегда ли зло 
:(
    Опции темы
carper
Дата 15.2.2011, 12:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 227
Регистрация: 2.3.2005

Репутация: нет
Всего: 8



Да, я знаю, что JUNIT в основном служит для модульного тестирования, а не интеграционного или функционального.
И, да, я знаю (и использую) про TestNG.

Да, я знаю, что взаимозависимость модульных тестов есть зло.

Но, хотелось бы услышать, как правильно решить простейшую проблему средствами JUNIT - пусть есть класс с 2-мя методами: addRecord(...) и  delRecord(String idrecord);

Хотелось бы протестировать оба метода на реальном экземпляре базы данных.

Что пока приходит на ум:
1. Тестировать оба метода в ОДНОМ тестовом:
 addRecord(...);
 ....
 delRecord(String idrecord);

 Не здорово хотя бы с точки зрения названия тестового метода и явного внесения зависимости тестирования 
delRecord  от того отработает ли addRecord.

2. Попробовать левыми способами реализовать удаление (для очистки таблиц после отработки теста  addRecord(...)) или добавление (для последующего тестирования delRecord).
Тогда придется ломиться в базу напрямую, что не всегда возможно без написания специальной процедуры в самой базе, т.к., в общем случае, добавление/удаление записи на уровне СУБД совсем не обязательно элементарная процедура (например, необходимо вызвать хранимую процедуру/ы + отработает ряд тригерров и т.п. + последовательность вызова этих самых процедур частично лежит на стороне как раз методов JAVA ..).

3. Наплевать и написать mock базу/таблицы, где снимаются возражения по п.2.
Долго и на практике приводит к попыткам просто не тестировать.
Да и работа с тестовыми таблицами не всегда позволит отловить ошибки типа чрезмерно долгого отрабатывания
запросов.

4. Сделать умное лицо и реализовать способ 1 под флагом функционального теста.  smile

PM MAIL   Вверх
LSD
Дата 15.2.2011, 17:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Leprechaun Software Developer
****


Профиль
Группа: Модератор
Сообщений: 15718
Регистрация: 24.3.2004
Где: Dublin

Репутация: 1
Всего: 538



Для этих целей существуют DbUnit и SQLUnit. Они приводят базу в требуемое состояние перед запуском теста и возвращают в исходное по окончанию.


--------------------
Disclaimer: this post contains explicit depictions of personal opinion. So, if it sounds sarcastic, don't take it seriously. If it sounds dangerous, do not try this at home or at all. And if it offends you, just don't read it.
PM MAIL WWW   Вверх
carper
Дата 15.2.2011, 17:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 227
Регистрация: 2.3.2005

Репутация: нет
Всего: 8



Цитата
 LSD 


И как DbUnit и SQLUnit решают данную проблему?

Получаем способ N2, ну разве что будем ломиться к базе не напрямую, а сперва запустим DbUnit из которого, опять же каким-то левым (для приложения, разумеется, а не DbUnit) способом, будем ломиться к базе.

Почему "левым"?, а потому, что единственный не левый как раз и реализован в методах addRecord и delRecord.

Погуглил тут и выяснил, что давно идет дискуссия между сторонниками атомарности unit тестов, которые говорят абсолютно правильные слова и их противниками, которые указывают, что на практике иногда такое решение рождает неповоротливых, долго выполняющихся монстров.

Правда доводы последних зачастую сводятся к тому, что они пытаются реализовать интеграционные/функциональные тесты, принимают их почему-то за юнит тесты и возмущаются. smile

Но иногда ...

Вот у меня получается, что, например, метод addRecord должен добавить запись в базу и всё.
Казалось бы тестируй себе, зачем тебе delRecord?

А хотя бы затем, что тест для addRecord должен за собой подчистить базу, а грамотно можно это сделать либо вызвав в ЭТОМ же методе  delRecord, или специально для этого случая написать отдельный, свой delTestRecord, который, опять же, надо бы протестировать.

А delRecord? И плевать бы ему на то есть ли вообще addRecord, да вот беда - удалять-то можно только то, что уже есть .... ну и далее по кругу.


PM MAIL   Вверх
LSD
Дата 15.2.2011, 18:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Leprechaun Software Developer
****


Профиль
Группа: Модератор
Сообщений: 15718
Регистрация: 24.3.2004
Где: Dublin

Репутация: 1
Всего: 538



Это не "левый способ", а set up и tead down smile 

Проблема первого способа, в том что:
1. Если возникнет проблема в методе add(), то проблема в del() не будет выявлена.
2. del() завязывается на add(), например если ID записи генерируется при вставке и возвращается add(), то del() становится зависим от того насколько корректно было возвращен ID.


--------------------
Disclaimer: this post contains explicit depictions of personal opinion. So, if it sounds sarcastic, don't take it seriously. If it sounds dangerous, do not try this at home or at all. And if it offends you, just don't read it.
PM MAIL WWW   Вверх
carper
Дата 15.2.2011, 20:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 227
Регистрация: 2.3.2005

Репутация: нет
Всего: 8



Цитата
Это не "левый способ", а set up и tead down 


Увы, даже не это, т.к. могут тестироваться еще много методов, не нуждающихся в таких пост и предусловиях..

Похоже, что серебряной пули тут не найти, надо просто выбирать, что хуже.

Наверное, все же был прав автор TestNG, когда ввел возможность явно описывать зависимости между тестами, по сути мой метод 1, просто более элегантно оформленный.

Правда меня терзают сильные подозрения, что он имел в виду несколько иное, например, случай, когда тестирование при провале предыдущего теста не имеет смысла, а вовсе не давал инструмент для нарушения атомарности тестов. smile
PM MAIL   Вверх
LSD
Дата 16.2.2011, 11:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Leprechaun Software Developer
****


Профиль
Группа: Модератор
Сообщений: 15718
Регистрация: 24.3.2004
Где: Dublin

Репутация: 1
Всего: 538



Юнит тест должен выполнятся в неком синтетическом, контролируемом окружении - моки, или специально подготовленная база. И то что ты выполняешь тесты на реальной базе, это уже нарушении идеологии. Поэтому в данном случае set up и tead down это наименьшее из зол.


Что ты будешь делать если у тебя появится несколько методов del(), по ID, по name и т.д.? Будешь несколько раз вызывать add()?


--------------------
Disclaimer: this post contains explicit depictions of personal opinion. So, if it sounds sarcastic, don't take it seriously. If it sounds dangerous, do not try this at home or at all. And if it offends you, just don't read it.
PM MAIL WWW   Вверх
batigoal
Дата 16.2.2011, 12:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Нелетучий Мыш
****


Профиль
Группа: Участник Клуба
Сообщений: 6423
Регистрация: 28.12.2004
Где: Санктъ-Петербургъ

Репутация: нет
Всего: 151



LSD, как я понимаю, у carper нет задачи сделать юнит-тест. Он выполняет интеграционный тест средствами JUnit - вполне нормальная задача.


--------------------
"Чтобы правильно задать вопрос, нужно знать большую часть ответа" (Р. Шекли)
ЖоржЖЖ
PM WWW   Вверх
carper
Дата 16.2.2011, 14:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 227
Регистрация: 2.3.2005

Репутация: нет
Всего: 8



Цитата
  batigoal... Он выполняет интеграционный тест средствами JUnit - вполне нормальная задача. 


Был бы это обычный интеграционный тест, то и вопросов бы не было.

Я привел предусловия: хочу протестировать каждый метод отдельно, тем более, что они взаимонезависимы - чтобы что-то добавить не нужен метод для удаления, чтобы что-то удалить не нужен метод для добавления.

С другой стороны для этого придется реализовать функционал обоих этих методов каким-либо еще, третьим способом и не хочется тратить на написание теста в 10 раз больше усилий, чем для создания метода.

Поэтому хотел бы понять, а нет ли логической ошибки в рассуждениях - в конце-концов тестирование операций типа Create Update Delete (не обязательно именно в базе)  очень частая операция. 


Цитата
  LSD
Что ты будешь делать если у тебя появится несколько методов del(), по ID, по name и т.д.? Будешь несколько раз вызывать add()? 


В первом посте предложил несколько своих кривых вариантов, один из них как раз именно такой - несколько раз вызывать add(). smile

И как раз хочу понять, а как правильно?

Если единственный ответ - да, надо продублировать функционал обоих методов каким-то третьим способом, то вопросов больше нет. 
Разве что этот третий способ, в свою очередь не застрахован от ошибок!
Вот и будет, например, получаться, что в тестовой среде запись будет успешно удаляться только потому, что при ее вставке для теста третьим способом забыл добавить что-то в одну из 15 взаимосвязанных таблиц.

А где дублировать этот функционал в тестовой базе, tearUp  и прочем, это уже не так важно.


PM MAIL   Вверх
LSD
Дата 16.2.2011, 20:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Leprechaun Software Developer
****


Профиль
Группа: Модератор
Сообщений: 15718
Регистрация: 24.3.2004
Где: Dublin

Репутация: 1
Всего: 538



Нет никакого дублирования функционала. Подготовка входных параметров, подготовка среды, проверка результата - это все часть теста. Юнит тест вообще ничего не должен знать об устройстве системы, о том, что методы add() выполняют противоположные действия dell().

И кстати, как ты проверяешь, что данные добавить/удалились? Через get() метод?


--------------------
Disclaimer: this post contains explicit depictions of personal opinion. So, if it sounds sarcastic, don't take it seriously. If it sounds dangerous, do not try this at home or at all. And if it offends you, just don't read it.
PM MAIL WWW   Вверх
carper
Дата 16.2.2011, 21:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 227
Регистрация: 2.3.2005

Репутация: нет
Всего: 8



Цитата

Подготовка входных параметров, подготовка среды, проверка результата - это все часть теста. Юнит тест вообще ничего не должен знать об устройстве системы, о том, что методы add() выполняют противоположные действия dell().


Подпишусь под каждым словом.

А теперь как же на практике реализовать то, что я написал?

Обычный псевдокод.

Например:
Код

  @Before
  public void before() {
    //СПЕЦ метод, только для теста
    добавитьЗапись(); //Как? SQL в 15 таблиц, можно, но сложно и можно что-то забыть
  }

  @After
  public void after() {
     //СПЕЦ метод, только для теста
     почиститьТаблицы(); //Как? SQL в 15 таблиц + дернуть пару хранимок
  }

  /* Вопрос "как?" тут ключевой */
  //Тот же тестовый добавитьЗапись() будет содержать код либо совпадающий с
  //"рабочим"  addRecord() либо какой-то свой код, который надо тестировать на полноценность
  //добавления этой самой записи.
  //Если первое, то вообще самообман какой-то получается. :(


Пока для обеспечения чистоты эксперимента видится все же создание тестовых таблиц записи в которых будут заполняться при помощи непроверенного кода.

Тогда юнит тесты пройдут, а остальное ловим на интеграционных/функциональных тестах.

Какой-то неприятный вариант получается, мне так кажется. Нет?
PM MAIL   Вверх
LSD
Дата 17.2.2011, 16:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Leprechaun Software Developer
****


Профиль
Группа: Модератор
Сообщений: 15718
Регистрация: 24.3.2004
Где: Dublin

Репутация: 1
Всего: 538



Цитата(carper @  16.2.2011,  22:00 Найти цитируемый пост)
SQL в 15 таблиц, можно, но сложно и можно что-то забыть

Цитата(carper @  16.2.2011,  22:00 Найти цитируемый пост)
SQL в 15 таблиц + дернуть пару хранимок

Тестирование сложной бизнес логики, требует достаточно сложных проверок. Ты еще не учел, что тебе после того как вызовешь add()/dell() надо будет проверить эти 15 таблиц, на предмет добавились ли данные.


--------------------
Disclaimer: this post contains explicit depictions of personal opinion. So, if it sounds sarcastic, don't take it seriously. If it sounds dangerous, do not try this at home or at all. And if it offends you, just don't read it.
PM MAIL WWW   Вверх
carper
Дата 22.2.2011, 17:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 227
Регистрация: 2.3.2005

Репутация: нет
Всего: 8



Хм...
Интересно, а как проводить даже интеграционный тест, если имеется бин "A" со временем жизни singleton, использующий бин "B" со временем жизни session.

Причем интересует правильно ли инициализируется бин "B" в "A".

Вопрос не потому, что хочется тестировать внутренний функционал Spring, а потому, что бин "A" предполагается использовать в отдельном потоке и тут возможны нюансы.

Т.е. бину можно устроить в тесте нечто вроде:

@Autowired
BeanA beanA_1;

@Autowired
BeanA beanA_2;

Но для полноценного теста надо проверить работу с разными сессиями, а при вышеназванном варианте, получается, что beanA_1.getBeanB() == beanA_2.getBeanB();

Можно получать бины А из фабрики - сути это не меняет, т.е. для теста не годится. :(

PM MAIL   Вверх
LSD
Дата 22.2.2011, 19:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Leprechaun Software Developer
****


Профиль
Группа: Модератор
Сообщений: 15718
Регистрация: 24.3.2004
Где: Dublin

Репутация: 1
Всего: 538



Цитата(carper @  22.2.2011,  17:57 Найти цитируемый пост)
Интересно, а как проводить даже интеграционный тест, если имеется бин "A" со временем жизни singleton, использующий бин "B" со временем жизни session.

А откуда бин А берет бин В?


--------------------
Disclaimer: this post contains explicit depictions of personal opinion. So, if it sounds sarcastic, don't take it seriously. If it sounds dangerous, do not try this at home or at all. And if it offends you, just don't read it.
PM MAIL WWW   Вверх
carper
Дата 24.2.2011, 09:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 227
Регистрация: 2.3.2005

Репутация: нет
Всего: 8



Код


//Вот бин A

@Service("binA")
@Scope("singleton")
public final class SeparateThreadRunner {
   @Autowired(required = true)
   private BeanB beanB;
   .....
   private void runInSeparateThread() //! {
      doJob(beanB); //Этот метод запустит отдельный поток  в котором и хотелось бы протестировать, что
                             //для разных сессий будет разный бин B, т.к. очень легко напортачить, изменив время
                             //жизни бина B и, без тестов, и не заметить что что-то стало врать!
                             //Именно поэтому тестирование ручками не подходит. :(
   }
....



//Вот бин B (по сути контейнер с методами для его обработки, ничего сложного, просто контейнер должен быть
//свой для каждой сессии. Разумеется, можно обойтись и без этого и просто класть нужный контейнер в 
//сессию, тогда классу можно сделать время жизни singleton, но так делать не хочется.)

@Service("binB")
@Scope(value = "session")
public class UUIDUtils implements Serializable {
....




PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Java: Design, Quality, Testing | Следующая тема »


 




[ Время генерации скрипта: 0.1037 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.