Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Java Новый топик    Ответить
 Помогите танкисту с TDD  [new]
DDiver
Member

Откуда: Россия
Сообщений: 327
У меня два фундаментальных вопроса, поэтому вопрос как бы из двух частей.

Допустим у нас есть интерфейс, для примера возьмём один метод
public interface TableBuilder {
...
    void addRowItem(String itemValue);
...
}

как пишут очень вумные дядьки в книжках, для написания кода этого метода нужны приблизительно следующие методы в тесте
public class TableBuilderTest {
...
    @Test
    public void addOneRowItem() {...}

    @Test
    public void addTwoRowItem() {...}

    @Test(expected = RowItemsCountMoreThanAvailableColumnsException.class)
    public void addTwoRowItemsIntoTableWithOneColumnWillThrowException() {...}

    @Test(expected = IllegalArgumentException.class)
    public void addRowItemWithoutValueWillThrowException() {...}
...
}

Действия веду в правильном направлении???

Нужно ли писать два метода: который добавляет один элемент и два или более? Или же можно сразу написать метод который добавляет несколько элементов и проверить корректность?

Лучше сразу описывать все возможные варианты поведения для метода, или например сначала протестировать основные, а потом(после основных тестов) всякие исключения и прочие отклонения от нормы?


Ну и вторая часть:
Этот мой класс при работе использует некий другой объект. И для проверки правильности отработки метода, приходиться писать вот такую шнягу
assertEquals(0, grid.getRows().getChildren().get(0).getChildren().size());

Хотел использовать моки, но не смог понять как проверять такого рода вещи.
Например добавление одной ячейки в строку выглядит вот так
        actualRow = new Row();
        Cell cell = new Cell();
        Label label = new Label(cellData);
        label.setParent(cell);
        cell.setParent(actualRow);
        actualRow.setParent(rows);

и как проверить что метод сделал что должен, я просто не понимаю.
пока не придумал как иначе, приходится делать вот так
assertEquals("CellData1", ((Label) grid.getRows().getChildren().get(0).getChildren().get(0).getChildren().get(0)).getValue());
но это же ппц :(


-----
Если дела идут плохо, есть вероятность, что в ближайшее время они пойдут ещё хуже.(с)Мерфи
13 янв 15, 21:33    [17113337]     Ответить | Цитировать Сообщить модератору
 Re: Помогите танкисту с TDD  [new]
For All
Member

Откуда:
Сообщений: 461
Надо написать несколько отдельных тестов:
1. тест на Cell о том, что Label в этот Cell добавляется
2. тест на Row о том, что Cell в этот Row добавляется
2. тест на Table о том, что Row в этот Table добавляется
14 янв 15, 14:36    [17116879]     Ответить | Цитировать Сообщить модератору
 Re: Помогите танкисту с TDD  [new]
mayton
Member

Откуда: loopback
Сообщений: 43358
DDiver, я просто скажу несколько ИМХ.

Одна имха. Ты немножко зашёл далеко. Тест на интерфейс это ближе к контракту. Вообще в твоём
случае глядя на интерфейс TableBuilder совершенно невозможно придумать его поведение. Как он должен
добавлять RowItem. А должен ли вообще? Может он его фильтрует? Вобщем непонятно.

Вторая имха. Тест должен исходить из того что затраты на сам тест будут хотя-бы не более чем 20%
от затрат на кодинг самог вычислительно блока. Ну где-то как 80:20. Закон Парето вобщем-то.
В твоём случае получается где-то 55:45 вобщем полная лажа.

При таком подходе вероятность "запороть бок" в тесте практически такая-же как и в коде. Тоесть твой
тест в свою очередь тоже надо покрыть тестом который надо покрыть тестом.... и так далее.

Вобщем тестить надо ПРОСТЫМ образом СЛОЖНЫЕ чёрные ящики.

Усёк?
14 янв 15, 19:08    [17118912]     Ответить | Цитировать Сообщить модератору
 Re: Помогите танкисту с TDD  [new]
Leonid Kudryavtsev
Member

Откуда:
Сообщений: 8078
mayton
Вторая имха.....

Ну IMHO оно и на то и IMHO... Но я очень давно слышал кардинально другую точку зрения )))
14 янв 15, 19:16    [17118942]     Ответить | Цитировать Сообщить модератору
 Re: Помогите танкисту с TDD  [new]
mayton
Member

Откуда: loopback
Сообщений: 43358
Давай и другую точку зрения заслушаем. Чего уж тут елейничать.
14 янв 15, 19:22    [17118959]     Ответить | Цитировать Сообщить модератору
 Re: Помогите танкисту с TDD  [new]
DDiver
Member

Откуда: Россия
Сообщений: 327
mayton,

По поводу первого ИМХА, это просто кусок интерфейса, тестирую я конечно же конкретную имплиментацию. Там методов больше и из-за этого более понятно что и для чего.

По второму, если я правильно понимаю, то сама метода ТДД используется для разработки классов любой сложности, а не только чёрных ящиков. Проблема в тестировании только из-за того, что мой класс использует для работы сторонний класс, который скажем так не очень testable. Так же скорее всего тут так же сыграло не в мою пользу моя собственная неопытность, возможно можно было как-то иначе всё выстроить.

Для понимания, класс скрывает низкоуровневую работу с GUI элементом Grid, и позволяет просто и лаконично отрисовыать таблицу с шапкой любой сложности (это чисто для моих внутренних нужд)
Используя такой подход, я смог ни разу не запустив GUI написать этот класс, который с первого же раза заработал как нужно и без ошибок. Т.е. время на отлов ошибок в GUI я потратил на написание тестов.
Ещё один плюс, что я теперь спокойно могу что-то менять и рефакторить в классе, и после тестов быть полностью уверенным что всё работает и я ничего не сломал правками.
Честно сказать, писать тяжко, т.к. постоянно хочется что-то сразу реализовать с заделом на будущее, но по идеологии TDD нужно писать минимум, а потом уже рефакторить.

Неужели никто не использует такой подход? Или все пишут код, а потом уже какие-то тесты на него?
14 янв 15, 21:24    [17119475]     Ответить | Цитировать Сообщить модератору
 Re: Помогите танкисту с TDD  [new]
scf
Member

Откуда:
Сообщений: 1482
Ну я активно использую ТДД :-)
Правда, я склоняюсь больше к стилю BDD, так что буду писать со своей колокольни. Все ниженаписанное относится именно к юнит-тестам, интеграционные и приемочные оставим в стороне. Итак, исходя из примерно 10-летнего опыта коммерческой разработки на Java...

Общие соображения:
1. Тестировать имеет смысл в основном небольшие черные ящики. Если для теста требуется мок (или, еще хуже, несколько моков) - это либо ошибка дизайна, либо вы пытаетесь тестировать не логику, а glue код (который связывает между собой уже протестированные блоки.
Обоснование: Тесты имеет писать для кода, в котором высока вероятность ошибок и понятно поведение. Также тесты с моками хрупки и часто ломаются, т.к. зависят от большого кол-ва интерфейсов, а то и от реализаций.
2. 100% покрытие юнит-тестами - миф. После выполнения предыдущего пункта, стоимость (сложность написания и время, потраченное на исправление упавших тестов после доработок) юнит-тестов растет экспоненциально и они себя не окупают.
3. "Написание юнит-тестов улучшает структуру программы". Это верно, но верно без фанатизма. Структуру программы улучшают изолированные черные ящики со специфицированным входом и выходом. Также структуру программы улучшают маленькие методы, маленькие классы, минимум состояния в этих классах и разделение ответственности. Но если переборщить - то получается код, где всё построено на фабриках и интерфейсах (это придется делать, чтобы мОчить разные куски кода) . Его очень сложно понимать и поддерживать.

По первому примеру:
Сначала нужно сформулировать в голове требования к классу, который вы собираетесь писать. Потом изложить эти требования в коде. Потом писать реализацию. Кол-во и качество нужных требований (тестов) определяет программист, исходя из их важности и рисков. Чем важнее требование и чем больше шанс, что на этом месте может быть баг - тем более приоритетен этот тест. В примере с двумя элементами - если есть сомнения, что с одним элементом код будет вести себя не так, как с двумя, или вариант с одним элементом значительно проще для отладки - то да, нужно написать тест с одним элементом.

По второму примеру:
Этот API категории "сопля" выглядит ужасно. Я не совсем понимаю, как оно работает, но почему бы не сделать вот так:
grid.getRow(0).getColumnCount();

Если с API совсем-совсем ничего сделать нельзя, то я бы написал тест так:
assertEquals("CellData1", value(grid, 0, 0, 0));

private String value(Grid grid, int... ix) {
  Element e = grid.getRows();
  for (int i: ix) e = e.getChildren().get(i);
  return ((Label)e).getValue();
}

Или вообще придумал приспособление, которое проверяет всё дерево заголовков одним махом.

Еще умеет смысл глянуть вот сюда:
https://code.google.com/p/spock/ (для интернационалистов)
http://habrahabr.ru/post/137561/ (для патриотов)
14 янв 15, 21:55    [17119585]     Ответить | Цитировать Сообщить модератору
 Re: Помогите танкисту с TDD  [new]
DDiver
Member

Откуда: Россия
Сообщений: 327
Спасибо за развернутый ответ.

С тестирование изолированных классов и поджиков всё понятно, но такое возможно только если всё пишешь сам. Но мы же знаем, что в реальности без сторонних библиотек и фрейморков, это мазохизм. Вот тут и появляется нужда в тестах с моками и прочими извращениями.
Например есть у нас некая библиотека, со страшно корявым и не выразительным API (но по каким-то причинам нужна именно она). Логично будет написать свой класс с нормальным API, который будет скрывать всю корявость и прочие потроха библиотеки.
Разработчик либы уже протестил свой код, а нам же нужно проверить, что мы его корректно вызываем. Но из-за корявости API просто замокать методы библиотечного класса не получиться, т.к. это жуткий overhead. Приходится использовать реальный класс из библиотеки и проверять результат работы нашего класса, дёргая методы этой библии и проверяя её внутреннее состояние, точно зная что мы должны получить при тех или иных манипуляциях.

С помощью какого ещё подхода можно такое проверить?
Это как мне кажется уже не совсем юнит тестирование, тут наверное ближе к интеграционному.
Где можно почитать об этом, учитывая специфику solo developer, т.е. когда ты сам себе и чтец, и жнец и на дуду игрец.
16 янв 15, 09:53    [17125789]     Ответить | Цитировать Сообщить модератору
 Re: Помогите танкисту с TDD  [new]
scf
Member

Откуда:
Сообщений: 1482
DDiver,

Обертка вокруг существующей библиотеки - это и есть glue-код. Чего там можно протестировать? Что параметры были переданы в нужном порядке? Не уверен, что это достаточный повод писать юнит-тест. Если же в обертке есть сложный код, то его нужно изолировать и тестировать именно его. Я бы ограничился парой интеграционных тестов, проверяющих, что обертка "в целом работает".

Хороших книг по TDD/BDD не знаю. Видел книги из серии "объясняю, что это такое", а дальше - только личный опыт.
16 янв 15, 10:23    [17125978]     Ответить | Цитировать Сообщить модератору
 Re: Помогите танкисту с TDD  [new]
kmaw
Member [заблокирован]

Откуда: бобруйск
Сообщений: 24786
scf
Тесты имеет писать для кода, в котором высока вероятность ошибок и понятно поведение


полностью согласен. и не могу придумать иные случаи, когда они нужны
10 июл 15, 09:53    [17875393]     Ответить | Цитировать Сообщить модератору
 Re: Помогите танкисту с TDD  [new]
kmaw
Member [заблокирован]

Откуда: бобруйск
Сообщений: 24786
и тем более, как можно разрабатывать на основе тестов - я тоже в танке
10 июл 15, 09:54    [17875396]     Ответить | Цитировать Сообщить модератору
Все форумы / Java Ответить