среда, 5 февраля 2014 г.

Черновик. Атомарные тесты. Вебинар

Поступил вот такой вопрос:
Александр, большинство разработчиков (абсолютное) знает про тесты. Даже не применительно к разработке ПО. Испытания, отработка и т.д. Например, ошибки в запуска космических ракет часто списывают на «недостаточную отработку». И из-за этой мешанины «испытания» реальных технических систем путают с «тестами» ПО. А чего его, ПО, отрабатывать? У нас же в партии не может быть «бракованных кнопок» или «окон, которые не всегда закрываются»? Или исполнительных модулей, у которых стартовая скорость зависит от качества электричества? Какова роль «тестирования» в разработке ПО, если это не только «контроль ОТК»?

Вопрос на самом деле - ОБЪЁМНЫЙ.

И содержит множество подвопросов.

Попробую ответить "по пунктам".

Во-первых - существует ГРОМАДНОЕ ЗАБЛУЖДЕНИЕ, что программный продукт СОЗДАЁТСЯ ОДИН РАЗ. А дальше он лишь "тиражируется".

Это - ЗАБЛУЖДЕНИЕ.

Из него следует ещё одно ЗАБЛУЖДЕНИЕ - чего мол производители программ "так дерут за софт". Мол "написали ОДИН РАЗ программу" и дальше "пиши сидюки и дери деньги".

Это всё - ЗАБЛУЖДЕНИЯ.

Поговорю для начала о них.


Во-первых - НЕ ОДИН раз.

В каждый КОНКРЕТНЫЙ момент существует инстанцированная копия продукта в ДАННЫХ КОНКРЕТНЫХ УСЛОВИЯХ.

Таких как:
1. Конкретная машина пользователя ("железо").
2. Конкретная операционная система.
3.Конкретное "окружение". (Например стоит какой-нибудь Punto-Switcher или нет и т.д. и т.п)
4. Конкретные входные данные.
5. Конкретное "время суток" (у нас лет 16 назад была смешная история - "Гарант тормозил в конкретной организации после 18-ти часов. Выяснилось, что после 18-ти админы выключали сетевые принтера. А приложение пыталось обратиться к ним).
6. Наверное что-то ещё.

Таким образом!

Программа существует НЕ В ЕДИНСТВЕННОМ экземпляре. А в каждый конкретный момент на имеет СОБСТВЕННЫЙ УНИКАЛЬНЫЙ ЭКЗЕМПЛЯР.

И вообще говоря - все эти "экземпляры" - надо тестировать.

Тут правда начинается "комбинаторика" в духе "Це из Эн по Ка". И рекурсивно "Це из Эн по Ка".

Понятное дело, что все возможные "комбинации" - протестировать - АБСОЛЮТНО не возможно.

Не то, что "те ракеты". Шучу :-)

Собственно за СТАБИЛЬНОСТЬ всех этих "Це из Эн по Ка" производитель деньги и "дерёт". Чем БОЛЬШЕ вариантов ПРОТЕСТИРОВАНО и чем ВЫШЕ СТАБИЛЬНОСТЬ работы приложения, тем больше ТРУДА затрачено, и соответственно - тем больше и "дерут".

Это - НАДО ПОНИМАТЬ.

Тетерь - как же протестировать ВСЕ ЭТИ "Це из Эн по Ка"?

Понятное дело, что БАНАЛЬНЫМ перебором это сделать - НЕВОЗМОЖНО.

Уж ОЧЕНЬ МНОГО сочетаний.

Тут делается ОДНО БОЛЬШОЕ ДОПУЩЕНИЕ (вроде как "подтверждаемое практикой"). Что если "мы протестируем составляющие части" и их тесты ПРОЙДУТ, а потом мы "протестируем варианты "скручивания этих частей воедино"" и эти тесты ПРОЙДУТ, то "скорее всего" - мы протестируем 80% функционала системы.

При этом - "скорее всего", если тест "написан корректно" (вот например ссылка - http://18delphi.blogspot.ru/2013/04/blog-post.html) с "оглядкой на ТЗ" и "его тонкости" и с БОЛЬШИМ количеством Assert'ов, инвариантов и пост- и пред-условий, то он на 60% может быть протестирован на "данных выдуманных из головы".

И тут же СРАЗУ хочется сказать вот о каком моменте - "синергия ТЕСТИРУЕМОСТИ и ХОРОШЕЙ АРХИТЕКТУРЫ" (вот ссылка - http://18delphi.blogspot.ru/2013/04/blog-post_3844.html, и вот - http://programmingmindstream.blogspot.ru/2014/01/blog-post_626.html, и ещё вот - http://programmingmindstream.blogspot.ru/2014/01/blog-post_3560.html, ну и там "ещё ссылки есть").

Какова МЫСЛЬ? А мысль - она - до невозможного БАНАЛЬНА - если код ХОРОШО ТЕСТИРУЕТСЯ, то он и ХОРОШО ИСПОЛЬЗУЕТСЯ.

Если для использования кода вам надо "продираться" через ДЕБРИ сильносвязанных классов, которые "упрятаны где-то в кишки", то ТЕСТ - вы СКОРЕЕ ВСЕГО - НЕ НАПИШЕТЕ. И - "скорее всего" этот код - ТАК ЖЕ ТРУДНО использовать, как и ПИСАТЬ К НЕМУ ТЕСТ.

А вот ЕСЛИ вы СМОГЛИ написать ТЕСТ к коду, то "скорее всего" - вы можете этот код "очень просто" использовать.

Это всё была "вода и вводная".

Теперь заострим своё внимание на том - КАК ПИСАТЬ тесты и КАКИЕ ВЫГОДЫ они нам дают.

Хочется отметить вот какую вещь - тесты это не только СРЕДСТВО ПОВЕРКИ, но ещё и СРЕДСТВО ДОКУМЕНТИРОВАНИЯ кода.

Я вот что когда-то писал:

"Хочу сказать ещё одну "банальную" мысль. Уже НЕ РАЗ повторенную "классиками", но я ПРОЧУВСТВОВАЛ её на СЕБЕ.

Тесты это НЕ ТОЛЬКО (да и не столько) способ "поверки" правильности кода и его соответствия ТЗ.

Тесты это ещё и "пример использования" проектных классов и архитектурных решений.

У меня есть много кода, который покрыт тестами, но "написан давно" и НЕ ВМЕЩАЕТСЯ ни в хранилище "графических образов", ни в хранилище "строк кода" в моём мозгу.

Но! ПОСКОЛЬКУ этот код ХОРОШО и плотно покрыт тестами - мне и НЕ НАДО "его помнить".

Я ВСЕГДА могу посмотреть на ТЕСТЫ и понять - "как это работает" и "зачем это нужно".

ВСЁ! Мне НЕ НАДО "помнить код" и его детали.

Мне ДОСТАТОЧНО взят тесты и "перезагрузить" мой "кеш мозга"."

Вот ссылка - http://programmingmindstream.blogspot.ru/2014/01/tdd.html

Хочу отметить ещё одну мысль - тесты это не только средство поверки, но и ИНФРАСТРУКТУРА для СОЗДАНИЯ КОДА.

Вот цитата:

"В чём же Driven?

А в ТОМ, что тесты это не просто ИНСТРУМЕНТ для тестирования, и даже скажем так - это СОВСЕМ не инструменты для тестирования.

Это в ПЕРВУЮ очередь инструменты для разработки системы и "исследования её поведения".

ТЕСТЫ - это ОТЛИЧНОЕ поле для отладки. А ЛИШЬ потом - поле для "поверки регресса".

Ну в самом деле - "если тест написан - не выкидывать же его".

По крайней мере - пока он работает.

Ещё раз.

Тесты - ОБЕСПЕЧИВАЮТ в первую очередь - инфраструктуру для отладки. А уж потом - "меряют регресс".

Я наверное так долго - именно эту мысль и пытался донести.

И ещё раз. ТЕСТЫ - инфраструктура для отладки. В ПЕРВУЮ очередь"

Вот ссылка - http://programmingmindstream.blogspot.ru/2013/11/tdd.html

Итак!

Подведём "прмежуточный итог".

Тесты это:

1. Способ поверки функциональности программного обеспечения.
2. Способ выявления неоднозначностей и противоречивостей ТЗ.
3. Способ "выравнивания" АРХИТЕКТУРЫ.
4. Способ ДОКУМЕНТИРОВАНИЯ кода.
5. Инфраструктура для разработки кода.

ЗАФИКСИРУЕМ это.

Теперь всё же - "то и как".

Давайте для начала посмотрим на "уровни тестирования".

Вот они:

1. Тестируем "базовые классы" и "структуры данных". Тут для использования подходит "голый DUnit" (http://dunit.sourceforge.net/) и  "атомарные тесты" - http://18delphi.blogspot.ru/2013/04/blog-post_2326.html и http://programmingmindstream.blogspot.ru/2013/11/tdd_28.html. Не забываем про "пред- и пост-условия".

2. Тестировать надо классы с "параметризованным входом" (входные mock'и). Тут подходит вот какая надстройка над DUnit - http://18delphi.blogspot.ru/2013/04/blog-post_7108.html, а также - "использование эталонов" - http://18delphi.blogspot.ru/2013/04/blog-post_8791.html

3. Тестировать надо "пользовательские истории" ("базовых классов"). Тут подходит вот что - http://roman.yankovsky.me/?p=1355. Может быть также можно тестировать и "прецеденты приложения", но Роман эту тему пока не развил.

4. Тестировать надо "отдельностоящие компоненты". Тут подходит вот что - http://programmingmindstream.blogspot.ru/2014/01/openctf-component-test-framework.html

5. Тестировать надо "прецеденты приложения". Тут подходит вот что - http://18delphi.blogspot.ru/2013/11/gui.html и http://18delphi.blogspot.ru/2013/11/gui-back-to-basics_22.htmlи http://18delphi.blogspot.ru/2013/11/gui-dunit.html

6. Тестировать надо GUI-приложение как "чёрный" ("серый") ящик ("Честное" нажимание на "кнопки"). Тут подходит - TestComplete (http://smartbear.com/products/qa-tools/automated-testing-tools/). Ну и не забываем про - http://programmingmindstream.blogspot.ru/2014/01/blog-post_626.html (там про "серый ящик" в частности написано).

7. Тестировать надо GUI-приложение как "реальный пользователь" ("автоматам" мы не доверяем). Что тут? Тут - РУЧНОЕ ТЕСТИРОВАНИЕ. "Никаких инструментов"... Единственный инструмент это что-то вроде RequisitePro (http://www-03.ibm.com/software/products/ru/reqpro/) и написание HLTC (www.qualitytesting.info/forum/topics/low-and-high-level-test-cases?commentId=2064344%3AComment%3A427950&xg_source=activity http://pic.dhe.ibm.com/infocenter/rqmhelp/v2r0/index.jsp?topic=/com.ibm.rational.test.qm.doc/topics/c_testcases.html http://www.sqaforums.com/showflat.php?Number=663587) для "живых тестеров".

Вот ссылка - http://programmingmindstream.blogspot.ru/2014/01/blog-post_27.html

Что тут ВАЖНО ОТМЕТИТЬ? А вот что:

"ПРЕДПОЧТИТЕЛЬНОСТЬ тестов возрастает при движении от 7-го пункта к 1-му.

Почему?

Потому что СЛОЖНОСТЬ тестов возрастает в ОБРАТНОМ порядке - при движении от 1-го пункта к 7-му.

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

Есть ОДНО (очень важное) ИСКЛЮЧЕНИЕ - если "подобный тест" УЖЕ НАПИСАН. Тогда - уровень теста - НЕ ВАЖЕН. Надо взять УЖЕ СУЩЕСТВУЮЩИЙ тест и попытаться АДАПТИРОВАТЬ его к "новому тестируемому случаю"."

ЗАФИКСИРУЕМ и ЭТО тоже.

Ещё хочется сказать вот что - тесты из первого пункта бывают "совсем атомарные" или "комплексные".

"Атомарные" тесты - конечно - ПРЕДПОЧТИТЕЛЬНЕЕ "комплексных".

"Атомарные тесты" тестируют БАЗОВУЮ ФУНКЦИОНАЛЬНОСТЬ, а "комплексные" - тестируют уже "способы интеграции" этой самой "базовой функциональности".

Скажем так - "комплексные тесты" это самая "первая ступень" к тем самым "Це из Эн по Ка".

Какие отличия "атомарных" и "комплексных" тестов.

Вот цитата:

""Комплексные тесты" - продолжительные по времени, да и "отлаживать их тяжело".

Вот тут на помощь приходят "атомарные тесты".

Которые - "очень маленькие". Буквально "две три строки".

Что я делаю?

Я сначала запускаю "атомарные тесты" и смотрю, что "в них ничего не сломалось".

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

Если "сломалось", то я вычленяю проблему и пишу "атомарный тест".

ЕЩЁ ОДИН.

И "отлаживаюсь" на комплекте "атомарных тестов".

Которые "проходят за микросекунды".

Входные и выходные данные я для них придумываю "из головы".

Когда я отладился на "атомарных тестах" - я опять пускаю "комплексные тесты".

Ну и процесс - ИТЕРАТИВНЫЙ.

И "по ходу пьесы набирается набор "атомарных тестов", которые позволяют "всё реже и реже" пускать "комплексные тесты"".
"

Ссылка вот - http://programmingmindstream.blogspot.ru/2014/01/blog-post_31.html

ЗАФИКСИРУЕМ и это.

Давайте "немножко забежим вперёд" (чтобы не было так скучно).

Рассмотрим пункты - 4, 5, 6.

Тут появляются "свои уровни тестирования".

Вот они:

"1. Непосредственная посылка сообщения от мыши или клавиатуру.
2. Вызов Click или его аналогов непосредственно на интересующем нас контроле.
3. Вызов связанного TAction.
4. Вызов функционала бизнес-логики напрямую.

Каждый из перечисленных подходов - имеет право на жизнь и более того - служит для тестирования разных вещей.

"Непосредственная посылка сообщения от мыши или клавиатуру." - тестирует, что?
Это тестирует, что "реальный" ввод доходит до нашего контрола.

"Вызов OnClick или его аналогов непосредственно на интересующем нас контроле." - тестирует что?
Это тестирует, что у контрола определён метод Click и что он связан с нужной функциональностью.

"Вызов связанного TAction." - тестирует что?
Это тестирует тот факт, что в системе существует нужный TAction с нужной функциональностью. Пусть и не привязанной к контролам.

"Вызов функционала бизнес-логики напрямую." - тестирует что?
Это тестирует тот факт, что в системе есть нужная бизнес-логика, пусть и не привязанная к TAction или контролам, и что эта бизнес логика выполняется в соответствии с ТЗ."

Вот ссылка - http://18delphi.blogspot.ru/2013/11/gui_9423.html

ЗАФИКСИРУЕМ и этот факт - тоже.

Теперь вернёмся к ПЕРВОМУ "уровню" из ПЕРВОГО списка:

"Тестируем "базовые классы" и "структуры данных". Тут для использования подходит "голый DUnit" (http://dunit.sourceforge.net/) и  "атомарные тесты" "

Вот об "атомарных тестах" далее и хочется поговорить.

Вот ссылки про "атомарные тесты" и их использование:

http://programmingmindstream.blogspot.ru/2013/12/test-driven-development-tdd.html
http://programmingmindstream.blogspot.ru/2013/12/tdd.html
http://programmingmindstream.blogspot.ru/2013/11/tdd_28.html

Теперь давайте всё это "переосмыслим" и приведём "простейший пример "атомарных тестов"".

Начнём (как водится) с "контейнерных классов".

Почему?

Во-первых - их "ПРОСТО" тестировать.
Во-вторых - на "контейнерных классах" строится весь ОСТАЛЬНОЙ код программного обеспечения. Ну как у Вирта - "СТРУКТУРЫ ДАННЫХ и алгоритмы".
В-третьих - на контейнерных классах "ПРОСТО" продемонстрировать "шаблонизируемость" и "параметризуемость" (обобщения aka Generic'и и параметризованная кодогенерация).

Вот со "структур данных" - мы и начнём, а потом перейдём к "алгоритмам".

А потом перейдём к "обобщению структур и алгоритмов" в духе GoF.

Что ещё хочу "попутно заметить" - "тесты это ОГЛОБЛИ, которыми телега привязана к лошади".

1. Лошадь это ТЗ.
2. Тесты это оглобли.
3. Код (функционал) это телега.

Вот ссылка - http://habrahabr.ru/post/206828/

... to be continued ...

12 комментариев:

  1. Спросили тут:

    Непонятно почему тесты - способ документирования кода

    Отвечу:

    коротко - потому, что тесты "белого ящика", а не "чёрного" - используют проектный код так как задумывалось его использовать
    т.е. ЛЮБОЙ тест БЕЛОГО ящика (а о БЕЛОМ - я там только и говорю) - это ПРИМЕР использования кода
    а стало быть - интерактивная документация

    ОтветитьУдалить
  2. Ещё вот спросили:

    Просто, я ведь со своей колокольни, со своего уровня смотрю :-) и в моём случае, тесты часто обьёмней ТЗ. То, что в ТЗ достаточно описать одной строкой, может потребовать целую группу тестов.

    Отвечу:
    ты прав

    но это во-первых от недостатка проработанности ТЗ.. это "порочная практика", но она - ЕСТЬ. От неё - никуда не деться.

    а во-вторых тесты всё равно объёмнее.. даже если ТЗ проработано
    ИМЕННО поэтому тесты ДОПОЛНЯЮТ картину

    РАСШИФРОВЫВАЮТ ТЗ, его тонкости и аспекты использования. И в итоге - ДОПОЛНЯЮТ ТЗ. Ну если в ИДЕАЛЕ.

    ОтветитьУдалить
  3. Ещё пишут о "высокоуровневых тестах":

    "Возможно, ты об этом писал раньше... А вдруг нет..: в общем, когда пишем алгоритм проверки части ТЗ, лучше эту часть комментом тут же и привести. Иногда, из ТЗ легко следует алгоритм, но обратное далеко не всегда справедливо. Можно через месяц прочитать алгоритм и сидеть думать: а что главное в тесте: пункт 5, 7 или 3? Или их конкретная связка, а отдельно проверяются в других местах? И т.д."

    Хорошее ЗАМЕЧАНИЕ.

    Конечно из тестов НАДО ДАВАТЬ ссылки на соответствующие части ТЗ.

    ОтветитьУдалить
  4. Ещё написали.

    "
    Вот есть часть:
    "Подведём "прмежуточный итог".

    Тесты это:

    1. Способ поверки функциональности программного обеспечения.
    2. Способ выявления неоднозначностей и противоречивостей ТЗ.
    3. Способ "выравнивания" АРХИТЕКТУРЫ.
    4. Способ ДОКУМЕНТИРОВАНИЯ кода.
    5. Инфраструктура для разработки кода."

    тут повествование НАДО БЫЛО ПРЕРВАТЬ и всё дальнейшее писать ОТДЕЛЬНЫМ постом."

    Пожалуй - соглашусь.

    ОтветитьУдалить
  5. Здравствуйте, будете ли вы присутствовать на вебинаре Ника Ходжеса.
    http://www.delphifeeds.com/go/s/112773 ?
    Подскажите плз. что делать, если есть куча унаследованного кода, где банально форма, на ней Grid, DataSet, Query. Всё это подключается к датамодуля каждой BPL. А на каждой форме всё прописано в тупую в обработчиках событий. Это вообще реально как то тестировать, и стоит ли тестировать, скажем форму которая делает банальный insert, update одной записи таблицы?

    ОтветитьУдалить
    Ответы
    1. "Здравствуйте, будете ли вы присутствовать на вебинаре Ника Ходжеса. "

      Завтра, да? Я ПОСТАРАЮСЬ. Но ен ждите от меня вопросов/ответов. Я с английским - "более чем на ВЫ".

      "Подскажите плз. что делать, если есть куча унаследованного кода, где банально форма, на ней Grid, DataSet, Query. Всё это подключается к датамодуля каждой BPL. А на каждой форме всё прописано в тупую в обработчиках событий. Это вообще реально как то тестировать, и стоит ли тестировать, скажем форму которая делает банальный insert, update одной записи таблицы?"

      Это - ОТДЕЛЬНЫЙ СЛОЖНЫЙ вопрос. Если хотите - мы его потом обсудим ПРЕДМЕТНО.

      Удалить
    2. Вот тут - http://programmingmindstream.blogspot.ru/2014/02/blog-post_1679.html

      Я написал "затравку"...

      Удалить
    3. "Я с английским - "более чем на ВЫ"."
      -- fluent-English, особенно "в стиле Ника Ходжеса или скажем А. Бауэра" - совсем "не мой конёк".

      Удалить
    4. "Я написал "затравку"..."
      -- в общем - если есть желание "обсуждать ПРЕДМЕТНО" - пишите "в личку", раскрывайте "интерфейс границ сред"... Чем могу - с удовольствием помогу... Но пока "абстрактно" - общаться не готов...

      Удалить
  6. как говорил герой одного романа "Немного найдётся людей, с которыми мне приятно общаться. Вы - один из них".

    Цитата:
    "Если для использования кода вам надо "продираться" через ДЕБРИ сильносвязанных классов, которые "упрятаны где-то в кишки", то ТЕСТ - вы СКОРЕЕ ВСЕГО - НЕ НАПИШЕТЕ. И - "скорее всего" этот код - ТАК ЖЕ ТРУДНО использовать, как и ПИСАТЬ К НЕМУ ТЕСТ."

    Т.е. условием тестируемости (факта возможности существования атомарных тестов) является спец-архитектура. Ну ладно, структура. Ииииии? Тесты уже не могут быть лошадь. Лошадью становится структура классов (на подмножестве шаблонов слабосвязного проектирования)? Более того, экспертиза головы Александра Валерьевича бо не написать "неправильный классы", которые не смогут есть "неправильный мёд" в виде тестов?

    Вопрос без оскорблений (чтобы не вызвать взаимные). Получается, нужна "культура производства кода", да? И какова она?

    Второй вопрос, вытекающий из первого. С одной стороны - атомарность тестов подразумевает "атомарность" классово/методового построения. Но ведь ТЗ в некотором смысле "интегрально". По крайней мере точно не доходит до атомарно-тестируемых методов изолированных классов.

    В общем-то получается, что "без Люлина пока никуда"?
    В инженерной практике я один раз в жизни допустил ошибку (в раннем студенчестве). Нарисовал конструкцию "планетарного редуктора". Чертеж был реальным, но "неразбирабельным". Т.е. шестерни стояли на месте, но вынуть их было нельзя. Соответственно, изготовить и собрать тоже. Пришлось корпус "распиливать" пополам и делать сборную на винтах конструкцию.
    С классами/методами/иерархией также? Не пора ли уже поговорить про "слабую связность"? (после ответов на 2 предыдущих вопроса, если захотите :))

    ОтветитьУдалить
  7. «Получается, нужна "культура производства кода", да? И какова она?»
    -- Культура? Я не стал бы употреблять столь сильных слов.
    Методика. Нужна методика и определённые технические приёмы. Инверсия зависимостей - один из них.

    «Второй вопрос, вытекающий из первого. С одной стороны - атомарность тестов подразумевает "атомарность" классово/методового построения. Но ведь ТЗ в некотором смысле "интегрально". По крайней мере точно не доходит до атомарно-тестируемых методов изолированных классов.»
    -- Безусловно, и в этом суть "корпускулярно-волнового дуализма" преследующего TDD.
    Получается, что тесты предназначены не для гарантий того, что продакшн будет свободен от ошибок, а для того, чтобы от них (точнее, от тех, которые контролируются тестами) были свободные фрагменты приложения: классы, процедуры, словом то, что "покрыто тестами".
    Искусство состоит в том, чтобы это всё сошлось и совместилось в итоге с ТЗ.
    Но программирование - не искусство.
    Потому я и не сторонник TDD, хотя у нас и применяются некоторые его подходы.

    «С классами/методами/иерархией также? Не пора ли уже поговорить про "слабую связность"? (после ответов на 2 предыдущих вопроса, если захотите :))»
    -- Да, IoC http://ru.wikipedia.org/wiki/IoC как раз об этом...
    Но как и многое другое в разработке ПО, это только один из подходов.
    Он НЕ безальтернативен...

    ОтветитьУдалить
    Ответы
    1. Я кстати скажу РОВНО ТАК ЖЕ:
      "Потому я и не сторонник TDD, хотя у нас и *применяются некоторые* его подходы."

      :-) Ещё РАЗ :-) Я НЕ СТОРОННИК TDD в его "КЛАССИЧЕСКОМ ВИДЕ", я лишь применяю НЕКОТОРЫЕ его подходы.

      Удалить