пятница, 28 февраля 2014 г.

Написать про "эталоны".

"а элементарно... генерируем псевдопоследовательность пар аргументов операции сложения.. прогоняем эту последовательность через тест сложения.. а результаты пишем в файл
[16:11:39] Люлин Александр Валерьевич: и на ПЕРВОМ прогоне тестов объявляем это ЭТАЛОНОМ
[16:11:59] Люлин Александр Валерьевич: а на последующих - просто сравниваем получившееся с ЭТАЛОНОМ
[16:12:11] Люлин Александр Валерьевич: это тест на СТАБИЛЬНОСТЬ поведения системы
[16:12:18] Люлин Александр Валерьевич: в рамках ТЗ
[16:12:31] Люлин Александр Валерьевич: тогда и сравнение double - не нужно
[16:12:59] Люлин Александр Валерьевич: но не факт, что операция сложения с калькуляторе должна быть реализована по "правилам Delphi"
[16:13:03] Люлин Александр Валерьевич: мысль понятна?" 

четверг, 27 февраля 2014 г.

Тестирование калькулятора. High Level Test Cases

Немножко в сторону.

Есть такая штука как "ручное протыкивание".

Как оно делается?

Тут варианты следующие:
1. Чисто ручное протыкивание "на интуиции".
2. Протыкивании на основании ТЗ и Test Case'ов.
3. Симбиоз этих двух подходов.

Я лично больше приемлю HLTC.

Почему? Потому что они являются прародителями не только "ручного протыкивания", но и "автоматических тестов".

Так вот пишет тут знакомый.

Цитирую:

"Заставил я себя HLTC соорудить:

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

HLTC1: Начало работы
HLTC1.1: При открытии программы:
HLTC1.1.1: Поле для ввода данных содержит 0
HLTC1.1.2: Все кнопки доступны(активны)

HLTC2: Ввод данных (цифр и разделителя)
HLTC2.1: Способы ввода
Убедиться, что данные можно ввести всеми способами:
HLTC2.1.1: Нажатие кнопок приложения с помощью указателя мыши
HLTC2.1.2: Ввод с основной клавиатуры
HLTC2.1.3: Ввод с дополнительной клавиатуры

HLTC2.2: Реакция системы на введённые данные:
HLTC2.2.1: Цифры
HLTC2.2.1.1: Отображаются в поле для ввода данных
HLTC2.2.1.2: Каждая следующая введённая цифра располагается справа от имеющихся
HLTC2.2.1.3: Первая введённая цифра заменяет собой умолчательный 0

HLTC2.2.2: Точка-разделитель дробной части
HLTC2.2.2.1: Отображается в поле для ввода данных точкой
HLTC2.2.2.2: Добавляется справа от цифры в поле (включая умолчательный 0)
HLTC2.2.2.3: В числе может быть только одна запятая
HLTC2.2.2.3.1: Кнопка с запятой дизейблится после первого нажатия
HLTC2.2.2.3.2: Вновь становится активной при:
HLTC2.2.2.3.2.1: Общем сбросе
HLTC2.2.2.3.2.2: Начале ввода следующего числа (после ввода операци)
HLTC2.2.2.3.3: При попытке ввода второго разделителя с клавиатуры - никак не реагируем


HLTC3: Операции

HLTC3.1: Способы ввода
Убедиться, что операции можно ввести всеми способами:
HLTC3.1.1: Нажатие кнопок приложения с помощью указателя мыши
HLTC3.1.2: Ввод с основной клавиатуры
HLTC3.1.3: Ввод с дополнительной клавиатуры

HLTC3.2: Арифметические операции (+ - * /):
HLTC3.2.1: Визуально никак не отображаются
HLTC3.2.2: Если последовательно ввести несколько операций - учитывается только последняя
HLTC3.2.3: После применения операции:
HLTC3.2.3.1: Первая введённая цифра заменяет отображаемое число
HLTC3.2.3.2: Ввод разделителя отображается как "0."

HLTC3.3: Получение результата:
HLTC3.3.1: Когда введено первое число, арифметическая операция и второе число - результат можно получить следующими способами:
HLTC3.3.1.1: Нажатием кнопки "=" в программе или на клавиатуре
HLTC3.3.1.2: Нажатием Enter с клавиатуры
HLTC3.3.1.3: Вызовом любой арифметической операции

HLTC3.4: Удаление последнего символа
HLTC3.4.1: Способы вызова:
HLTC3.4.1.1: Кнопка <-- (в нашем калькуляторе)
HLTC3.4.1.2: Клавиша клавиатуры Backspace
HLTC3.4.2: Единичное нажатие удаляет последний из введённых символов
HLTC3.4.3: При удалении последнего символа, он заменяется на 0
HLTC3.4.4: Попытки удалить 0 игнорируются
HLTC3.4.5: Не может быть применим к результату операции (вызов 
игнорируется)


HLTC3.5: Сброс
Отмена всех операций и замена текущего числа в поле на 0 производится следующими способами:
HLTC3.5.1: Кнопка "С" (в нашем калькуляторе)
HLTC3.5.2: Клавиша клавиатуры Esc

HLTC4: Специальные тесты
HLTC4.1: Арифметические операции с результатом
Когда в поле выведен результат предыдущей операции, с ним можно выполнить любое арифметическое действие.
Для этого нужно:
HLTC4.1.1: Если результат был выведен кнопками Enter или "=":
1. Нажать кнопку нужной операции
2. Ввести число
3. Любым способом вызвать результат

HLTC4.1.2: Если результат был выведен кнопкой арифметической операции:
1. Ввести число
2. Любым способом вызвать результат


HLTC4.2: Деление на 0
При попытке деления на 0, в поле выводится текст: "Значение не определено".


HLTC5: Завершение работы
HLTC5.1: Окно закрывается стандартными средствами ОС:
HLTC5.1.1: Крестик в правом верхнем углу
HLTC5.1.2: Горячие клавиши Alt+F4
HLTC5.2: С закрытием операции, из памяти должен выгрузиться процесс 

AL_calc.exe

Если решишь продолжить с калькулятором, может и пригодится :-)"

Я думаю, что со временем я разовью эту тему.

Про RUP (и другие IT-практики) и ... Украину

Сразу оговорюсь - "НИКОГО КОНКРЕТНО я в виду не имею".

Я лишь - "абстрактно рассуждаю".

Наблюдая что творится в том же Билайне или Сбербанке. Хотите реальных примеров - я могу их предоставить.

Теперь к делу.

Сейчас в свете "победившей революции" активно муссируется вопрос о "прямой демократии", люстрации и подобных вещах.

Я НЕ БУДУ говорить про Украину.

Я лишь скажу про Россию.

И почему это НЕВОЗМОЖНО.

Итак.

Что такое IT-практики?

Всякие RUP, Scrup, XP и т.д.

Это:

1. Вовлечённость.
2. Валидация.
3. Ответственность.

Повторю:

1. Вовлечённость.
2. Валидация.
3. Ответственность.

Теперь по пунктам.

Что КАЖДЫЙ из них ОЗНАЧАЕТ?

1. Вовлечённость - это не ПРОСТО УЧАСТИЕ в процессе формально, но и и ГОТОВНОСТЬ работать с ПОЛНОЙ ОТДАЧЕЙ.
2. Валидация - это проверка качества работы. Скорее всего - ДРУГИМИ людьми.
3. Ответственость - ну тут вроде понятно. Но я скажу. Ответственность это ПОНИМАНИЕ человека ТОГО ФАКТА, что если он НЕ ВОВЛЕЧЁН или НЕ прошёл ВАЛИДАЦИЮ, то его могут "ударить рублём" или (банально) "палкой по жопе".

Вот как я вижу ВСЕ IT-практики.

Для МЕНЯ - если нету ВОВЛЕЧЁННОСТИ, ВАЛИДАЦИИ и ОТВЕТСВЕННОСТИ, то - ПРАКТИКИ НЕ РАБОТАЮТ.

Теперь что мы имеем по факту?

Мы имеем "винтиков" и "представителей элит".

"Представители элит" почему-то "всё время заняты". Посему времени на ВОВЛЕЧЁННОСТЬ - у них БАНАЛЬНО НЕТ.

Но! Если НЕ ХВАТАЕТ времени - "делегируй полномочия"!

Но что "по факту получается".. Времени НЕТ, но и полномочия НЕ ДЕЛЕГИРУЕМ.

"Собака на сене".

Что сказать про ВАЛИДАЦИЮ? Зачастую КОРРЕКТНУЮ ВАЛИДАЦИЮ работы может провести лишь "технический специалист", который ПОДЧИНЁН человеку работу которого ВАЛИДИРУЕТ.

Почему? Потому что "начальник" НЕ ОБЛАДАЕТ теми самыми "техническими деталями" нужными для ВАЛИДАЦИИ. Да и ему зачастую НЕКОГДА (см. про ВОВЛЕЧЁННОСТЬ).

Всё хорошо. Но!

Вы много видели "начальников", которые ПОЗИТИВНО относятся к "критике с мест"?

Я - ВИДЕЛ. Но это - "не КАК ПРАВИЛО", это обычно - "потому что НАЧАЛЬНИК УМНЫЙ.. или даже МУДРЫЙ".

А в "целом" - это СОВСЕМ НЕ ТАК.

Теперь про ОТВЕТСТВЕННОСТЬ.

Про "конечных исполнителей" - тут всё понятно.. И могут "дать по жопе", "вызвать на ковёр" или в конце концов "лишить премии".

Что касается "элит"?

Кто может им "дать по жопе"? Когда они скажем не написали вовремя правилиьное ТЗ"? Или приняли "неверное решение" срывающее сроки?

Никто!

Только "вышестоящая элита". А ей зачастую - "некогда" ну или опять же ВАЛИДАЦИЮ могут проводить ТОЛЬКО "нижестоящие", но "кто их будет слушать"?

Так сказать - "корпоративная солидарность".

Как можно "заставить директора предприятия выполнять ИНСТРУКЦИИ написанные для НЕГО", когда ему БАНАЛЬНО НЕКОГДА?

Приведу пример НЕ из сферы IT.

У меня один близкий мне человек занимается GMP (сертификацией фарм-производств).

Так вот. Он рассказывает СТРАШНЫЕ вещи.

Там идея такая же:

1. ВОВЛЕЧЁННОСТЬ.
2. ВАЛИДАЦИЯ.
3. ОТВЕТСВЕННОСТЬ.

Так вот "это" в России - НЕ РАБОТАЕТ.

Пишутся инструкции, которые НЕ ВЫПОЛНЯЮТСЯ.

По причинам приведённым ВЫШЕ.

И что САМОЕ УЖАСНОЕ - это тот факт, что "иностранные инспекторы" - уже ДАВНО ОСОЗНАЛИ тот факт, что "в России это не работает".

И проверки проводятся с "учётом местной специфики".

Если я ВСЁ правильно понял.

Это вот ВСЕМ - повод ЗАДУМАТЬСЯ.

ЗАДУМАЙТЕСЬ - в ФАРМПРОИЗВОДСТВАХ - "инструкции пишутся, но НЕ ВЫПОЛНЯЮТСЯ".

И "сторонние инспекторы" на это "махнули рукой".

И это - НЕ ГОСУДАРСТВЕННЫЕ производства, а КОММЕРЧЕСКИЕ.

"Рука Путина" - тут (вроде бы) непричём.

Может стоит научиться "исполнять инструкции", "включать поворотники" и "пропускать пешеходов"?

Теперь к .. Украине. Точнее к "народной демократии", вече и майдану.

Что такое "прямая народная демократия"?

Это (как мне кажется):

1. Вовлечённость.
2. Валидация.
3. Ответственнось.

ВОВЛЕЧЁННОСТЬ - ты ДОЛЖЕН ГОЛОСОВАТЬ и высказывать СВОЁ МНЕНИЕ. Ты ОБЯЗАН это делать. Многие на это готовы?

ВАЛИДАЦИЯ - проверка КАЧЕСТВА вовлечённости. ТЫ ГОЛОСОВАЛ? ТЫ ЧЕМ ПОМОГ ФРОНТУ? Ты ЧЕСТНО высказал СВОЁ МНЕНИЕ? Ты ВООБЩЕ "это" делал?

ОТВЕТСТВЕННОСТЬ - ну тут БАНАЛЬНО. Если ты НЕ ГОЛОСОВАЛ, то "иди на лесоповал" (передёргиваю) или "отлучаем тебя от голосования" (тут НЕ ПЕРЕДЁРГИВАЮ). Тебя ВЫБРАЛИ? Ты ХОРОШО справился со своей задачей? Если ХОРОШО - "спи спокойно", а если ПЛОХО? Опять - "иди на лесоповал"... Ну или хотя бы на "общественные работы".

Отсидел ДЕПУТАТОМ два года. Тебе ИЗБИРАТЕЛИ поставили "плохо" - иди ДВА ГОДА "подметай улицы".

Мысль понятна?

Вот что называется "прямой народной демократией". Ну КАК Я "это" ВИЖУ.

Система с "отрицательной обратной связью".

Сделал ПЛОХО - будь добр сделать ХОРОШО.

Мы к "этому готовы"?

По-моему - НЕТ.

Мы в "своих офисах" не можем "ПРИВИТЬ IT-практики".

Не МОЖЕМ сделать так чтобы "люди не сидели на жопе".

Это в "рамках своих офисов".

А в рамках страны?

Я конечно ГЛУБОКО уважаю Украинцев и Украину. Но мне (почему-то кажется), что и ОНИ недалеко от нас ушли.

Я НЕ ВИДЕЛ ""прорывов" по внедрению IT-практик и на Украине тоже. Может быть я ПРОСТО про них НЕ ЗНАЮ.

Вот и подумайте...

Если мы не можем с помощью "прямой народной демократии" организовать СВОЮ ЖИЗНЬ "локально", пусть даже не в "офисе". В своём дворе.

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

Можем "локально"?

Тогда сможем и "глобально"!

Но я что-то "пока такого не наблюдаю".

Итак.

Повторю:

1. ВОВЛЕЧЁННОСТЬ.
2. ВАЛИДАЦИЯ.
3. ОТВЕТСВЕННОСТЬ.

Подумайте над этим.

1. ВОВЛЕЧЁННОСТЬ - КАЖДЫЙ должен быть "активным членом общества".
2. ВАЛИДАЦИЯ - "а судьи кто"?
3. ОТВЕТСТВЕННОСТЬ - КАЖДЫЙ должен знать, что он ОБЯЗАТЕЛЬНО понесёт ОТВЕТСВЕННОСТЬ за ДЕЙСТВИЯ или (что важно) - за БЕЗДЕЙСТВИЯ.

Это кстати и есть - "классический коммунизм".

P.S. http://18delphi.blogspot.ru/2013/05/rup.html

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

Тестирование калькулятора. Ещё "заметки со стороны"

"насколько я помню форму mdiChild нельзя сделать без главного окна, и стает вопрос как это сделать для тестирования ?"

http://programmingmindstream.blogspot.ru/2014/02/3.html?showComment=1393362618749#c902946886408328600

Отвечаю:

"Это в копилку вот к чему - http://programmingmindstream.blogspot.ru/2014/02/2.html?showComment=1393364099651#c6835117592870444331

Если форма создана ЗАКОННЫМИ СРЕДСТВАМИ приложения, то она - ТЕСТИРУЕМА.

Если хочется что-то "эмулировать" - то можно "помудрить". Например не ставить MDIChild в Design-time, а ставить его в Run-time.

А можно и не "мудрить", а сделать Factory Mathod или Dependency Injection."
http://programmingmindstream.blogspot.ru/2014/02/3.html?showComment=1393365591286#c1515178506539534679

Ещё РАЗ.

ЕСЛИ форма сделана ЗАКОННЫМИ методами, то она - ТЕСТИРУЕМА априори.

А если НЕ ЗАКОННЫМИ, то В ПЕРВУЮ очередь надо думать - КАК СДЕЛАТЬ её ЗАКОННЫМИ методами, а если не получается, то ДУМАТЬ о том, как НЕЗАКОННЫЕ методы сделать ЗАКОННЫМИ. Dependency Injection, Factory Method ну или на "худой конец" конструкторы С ПАРАМЕТРАМИ. Много есть "техник".

Мысль понятна?

вторник, 25 февраля 2014 г.

Тестируем калькулятор. ВАЖНОЕ дополнение. Как отделять ТЕСТОВЫЙ код от РЕАЛЬНОГО кода приложения

Как отделять ТЕСТОВЫЙ код от РЕАЛЬНОГО кода приложения.

Пока почитайте мой комментарий - http://programmingmindstream.blogspot.ru/2014/02/3.html?showComment=1393361203092#c8288255869892943793

ПОЗЖЕ я напишу и про это - ПОДРОБНО.

P.S. ОТДЕЛЬНОЕ СПАСИБО Товарищу Ingword за ХОРОШИЙ ВОПРОС.

Oftopic. Oftopic. Oftopic. Про Украину, Майдан и "недоигравших"

"Серьёзно" о том, что я думаю я написал в ФБ вот что:

"Об Украине...

Пошли с одной стороны ВОСТОРГИ, а с другой - ОПАСЕНИЯ, что вот "у нас начнут гайки закручивать"...

Скажу ЗА СЕБЯ - я НЕ ХОЧУ, чтобы пока я что-то "там программирую" пришли фашиствующие студенты и стали бы рассказывать в какой стране жить моим детям... по мне - пусть лучше Путин указывает... надеюсь, что он сделает ПРАВИЛЬНЫЕ выводы.. и из Межигорья тоже... Надеюсь, что ХВАТИТ ума у ВСЕХ понять, что социальный протест может ЗАПРОСТО перемежаться с УГОЛОВЩИНОЙ... И что если ЗАГНАТЬ В УГОЛ, то становится всё больше людей, которые говорят - "а как нам ИНАЧЕ бороться с системой, кроме как не палкой, коктейлем молотова и пулемётом"...

При этом я ОСТАЮСЬ при мнении, что ВСЁ ДОЛЖНО РЕШАТЬСЯ ПО ЗАКОНУ.. А не на площади.. И ТЕМ БОЛЕЕ не НА БАРРИКАДАХ...

И что ЛЮБЫХ ЛЮДЕЙ применяющих СИЛУ в отношении ЛЮБЫХ граждан - НАДО СУДИТЬ...

Пусть даже тех, кто БОРЕТСЯ за ПРАВЕДНЫЕ ИДЕАЛЫ... Но НЕПРАВЕДНЫМИ методами...

А уж за неправедные "идеалы" - тем более...

Да и к силовикам - это тоже относится.

Потому я и говорю - "надеюсь ХВАТИТ УМА"... ВСЕМ...

Вообще - всё ДОЛЖНО БЫТЬ ПО ЗАКОНУ. А не кто ГРОМЧЕ крикнет "любо" или "не любо".

И ТЕМ более не у "кого КУЛАКИ КРЕПЧЕ". В этом и состоит ОСНОВНАЯ ФУНКЦИЯ ГОСУДАРСТВА.

Я конечно наивен..."

"Несерьёзно" я напишу вот что:

Я СМОТРЕЛ многие видео с тренировками "майданщиков".

И что скажу - многие (и молодых) - либо "фентези"-мены, либо "единоборщики".

Из того, что я видел....

Работа с мечами...

Я конечно - "лох", но многое - знакомо...

Ребята - "недоиграли"...

Хотели на "настоящую войнушку"..

А тут вот она - "под боком"...

Я ПО СЕБЕ ЗНАЮ... Я вообще-то в армию собирался... Два деда полковники и всё такое...

Был разочарован, что "наши из афгана ушли".

Как же так... "Все революции и войны выиграны без нас"...

Так вот про майдан. Не про всех, но про "некоторых"...

Ребята "недоиграли"...

Хочется:

1. Быть ВАЖНЫМ членом общества.
2. Хочется показать молодецкую удаль.
3. Хочется поносить униформу и камуфляж, то есть "ленточки и бантики".

Знакомо...

Расскажу про "ленточки и бантики" и к чему они приводят:

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

Но я про "бантики и ленточки"..

когда-то на заре юности я носил армейский камуфляж моего дядьки и тельняшку ещё деда..
и водил я тогда детей в поход..
в Краснодар..
а в том же поезде ехали наши доблестные российские десантники..
в Гудауту..
на войну..
им нечего терять было...
и парни накачивались водкой..
и ходили мимо нас туда-сюда..
и в какой-то момент когда они основательно накачались - они стали цепляться ко мне и моим пионера - которые тоже были в тельняшках..
мол "мы тут за тельняшки кровь проливаем", а вы их "бесплатно носите"..
я им про деда даже не стал рассказывать ибо понял, что ГЛУПО..
и снял и камуфляж и тельник...
а мои пионеры полезли в бутылку..
мол "в свободной стране живём, что хотим то и носим"..
ну и всё кончилось тем, что меня (как СТАРШЕГО) и ещё одного пионера (повыше) вытащили в тамбур и долго били...
били. и поили водкой.. и опять били..
ну а остальные пионеры тельники сами поснимали.. когда ПОНЯЛИ,  что "дело пахнет керосином"....
а было мне тогда 19-ть лет.. и я отбивал своих пионеров - как мог..
плохо правда мог.. но все остались живы... и почти здоровы...
всё закончилось посадкой на поезд ОМОНа.. .. и снятием этих "героев" с поезда... Ну и прежде - неожиданно нарисовались два бывших афганца.. которые сначала пытались "разруливать ситуацию", а потом, когда поняли, что "не разруливается" - пошли к бригадиру поезда..
так что с тех пор мне "бантики и ленточки" - поперёк горла..
я если воевать пойду, то в трениках и олимпийке...

вот.. не дай бог вам решить, что я глумлюсь... особенно над погибшими... Дай бог, чтобы всё образовалось...

Потому и "улицы убирают" и "нацистские лозунги" мимо пропускают.. Потому что ВАЖНО БЫТЬ частью ОДНОГО ЦЕЛОГО... Пока не обожжёшься.. Или не ВЫИГРАЕШЬ...

понедельник, 24 февраля 2014 г.

Тестируем калькулятор №4. Меняем архитектуру приложения

Предыдущая серия была тут - http://programmingmindstream.blogspot.ru/2014/02/3.html

Продолжим наши посты.

Все исходники доступны тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/DraftsAndScketches/SomeTestProjects/DummyCalculator/Chapter4/

 Будем считать, что мы "обеспечили минимальное тестовое покрытие" всех прецедентов использования нашего приложения.

В этом допущении теперь можно поговорить об ИЗМЕНЕНИИ архитектуры приложения.

Введём "бизнес-класс" TCalculator в который соберём бизнес-логику приложения.

Вот он:

unit Calculator;

interface

type
 TCalculator = class
  public
   class function Add(const A, B: string): string;
   class function Sub(const A, B: string): string;
   class function Mul(const A, B: string): string;
   class function Divide(const A, B: string): string;
 end;//TCalculator

implementation

uses
  SysUtils
  ;

class function TCalculator.Add(const A, B: string): string;
var
  x1, x2, x3 : single;
begin
  x1 := StrToFloat(A);
  x2 := StrToFloat(B);
  x3 := x1 + x2;
  Result := FloatToStr(x3);
end;

class function TCalculator.Sub(const A, B: string): string;
var
  x1, x2, x3 : single;
begin
  x1 := StrToFloat(A);
  x2 := StrToFloat(B);
  x3 := x1 - x2;
  Result := FloatToStr(x3);
end;

class function TCalculator.Mul(const A, B: string): string;
var
  x1, x2, x3 : single;
begin
  x1 := StrToFloat(A);
  x2 := StrToFloat(B);
  x3 := x1 * x2;
  Result := FloatToStr(x3);
end;

class function TCalculator.Divide(const A, B: string): string;
var
  x1, x2, x3 : single;
begin
  x1 := StrToFloat(A);
  x2 := StrToFloat(B);
  x3 := x1 / x2;
  Result := FloatToStr(x3);
end;

end.

Теперь код главной формы проекта становится таким:

unit MainForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TfmMain = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  fmMain: TfmMain;

implementation

uses
  Calculator
  ;

{$R *.dfm}

procedure TfmMain.Button1Click(Sender: TObject);
begin
 Edit3.Text := TCalculator.Add(Edit1.Text, Edit2.Text);
end;

procedure TfmMain.Button2Click(Sender: TObject);
begin
 Edit3.Text := TCalculator.Sub(Edit1.Text, Edit2.Text);
end;

procedure TfmMain.Button3Click(Sender: TObject);
begin
 Edit3.Text := TCalculator.Mul(Edit1.Text, Edit2.Text);
end;

procedure TfmMain.Button4Click(Sender: TObject);
begin
 Edit3.Text := TCalculator.Divide(Edit1.Text, Edit2.Text);
end;

end.

Прогоняем наши тесты. Они - проходят.

Что выходит? Мы БЕЗБОЛЕЗНЕННО изменили архитектуру приложения и протестировали, что "ничего" не сломалось.

Идея понятна?

Мы СНАЧАЛА обеспечили "минимальное тестовое покрытие" ВСЕХ прецедентов использования приложения. А ТОЛЬКО ПОТОМ - поменяли его архитектуру.

Выделили класс с "бизнес-логикой". И проверили, что "НИЧЕГО не сломалось".

Почему МИНИМАЛЬНОЕ, потому что там "есть вопросы". Например со случайными данными - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/DraftsAndScketches/SomeTestProjects/DummyCalculator/Chapter4/Tests/GUI/RandomPlusTest.pas

"Почему" и "что" - мы ПОЗЖЕ обсудим.

Пока же смотрите на то, что есть. Надеюсь, что это будет вам полезно.

Тестируем калькулятор №3. Расширяем тестовое покрытие

Предыдущая серия была тут - http://programmingmindstream.blogspot.ru/2014/02/2.html

Все исходники доступны тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/DraftsAndScketches/SomeTestProjects/DummyCalculator/Chapter3/

В предыдущей серии мы добавили тест СЛОЖЕНИЯ.

Теперь добавим ещё три теста - ВЫЧИТАНИЯ, УМНОЖЕНИЯ и ДЕЛЕНИЯ.

Для начала немного переработаем наш TPlusTest.

Выделим ещё один АБСТРАКТНЫЙ тест - TOperationTest.

Вот он:

unit OperationTest;

interface

uses
  CalculatorGUITest,
  MainForm
  ;

type
  TOperation = (opAdd, opMinus, opMul, opDiv);

  TOperationTest = class(TCalculatorGUITest)
   protected
    procedure VisitForm(aForm: TfmMain); override;
    function  GetOp: TOperation; virtual; abstract;
  end;//TOperationTest

implementation

uses
  TestFrameWork,
  SysUtils
  ;

procedure TOperationTest.VisitForm(aForm: TfmMain);
const
 aA = 10;
 aB = 20;
begin
 aForm.Edit1.Text := IntToStr(aA);
 aForm.Edit2.Text := IntToStr(aB);
 case GetOp of
  opAdd:
  begin
   aForm.Button1.Click;
   Check(StrToFloat(aForm.Edit3.Text) = aA + aB);
  end;
  opMinus:
  begin
   aForm.Button2.Click;
   Check(StrToFloat(aForm.Edit3.Text) = aA - aB);
  end;
  opMul:
  begin
   aForm.Button3.Click;
   Check(StrToFloat(aForm.Edit3.Text) = aA * aB);
  end;
  opDiv:
  begin
   aForm.Button4.Click;
   Check(StrToFloat(aForm.Edit3.Text) = aA / aB);
  end;
 end;//case GetOp
end;

end.

А TPlusTest теперь принимает такой вид:

unit PlusTest;

interface

uses
  OperationTest
  ;

type
  TPlusTest = class(TOperationTest)
   protected
    function  GetOp: TOperation; override;
  end;//TPlusTest

implementation

uses
  TestFrameWork,
  SysUtils
  ;

function TPlusTest.GetOp: TOperation;
begin
 Result := opAdd;
end;

initialization
 TestFramework.RegisterTest(TPlusTest.Suite);

end.

Теперь добавляем TMinusTest, TMulTest и TDivTest:

unit MinusTest;

interface

uses
  OperationTest
  ;

type
  TMinusTest = class(TOperationTest)
   protected
    function  GetOp: TOperation; override;
  end;//TMinusTest

implementation

uses
  TestFrameWork,
  SysUtils
  ;

function TMinusTest.GetOp: TOperation;
begin
 Result := opMinus;
end;

initialization
 TestFramework.RegisterTest(TMinusTest.Suite);

end.

unit MulTest;

interface

uses
  OperationTest
  ;

type
  TMulTest = class(TOperationTest)
   protected
    function  GetOp: TOperation; override;
  end;//TMulTest

implementation

uses
  TestFrameWork,
  SysUtils
  ;

function TMulTest.GetOp: TOperation;
begin
 Result := opMul;
end;

initialization
 TestFramework.RegisterTest(TMulTest.Suite);

end.

unit DivTest;

interface

uses
  OperationTest
  ;

type
  TDivTest = class(TOperationTest)
   protected
    function  GetOp: TOperation; override;
  end;//TDivTest

implementation

uses
  TestFrameWork,
  SysUtils
  ;

function TDivTest.GetOp: TOperation;
begin
 Result := opDiv;
end;

initialization
 TestFramework.RegisterTest(TDivTest.Suite);

end.


И вот что получаем:


Что мы в итоге имеем?

Мы покрыли ВСЕ операции нашего калькулятора.

Так сказать "обеспечили минимальное тестовое покрытие" всех прецедентов использования нашего приложения.

Тут можно поговорить о том, что надо тестировать "разные наборы данных".

Или о тестировании "случайных наборов".

А также можно поговорить о тестировании граничных условий. Например деления на ноль.

Также можно говорить о тестировании валидации введённых данных.

Мы об этом поговорим в последующих постах.

Также мы поговорим об "изменении архитектуры". Мы уже почти вплотную подобрались к этому.

Поговорим. Но в следующих постах.

Пока возьму тайм-аут. А вы поглядите на то, что я вам предоставил.

Ну и почитайте вот что, если ещё не читали - http://programmingmindstream.blogspot.ru/2014/02/blog-post_4473.html

P.S. Попросили тут нарисовать UML-ДИАГРАММУ классов для данного приложения и его тестов. Это ИНТЕРЕСНО? НУЖНО? И можно ли обойтись ОДНОЙ лишь диаграммой классов? Или нужна ещё и sequence-диаграмма?

Тестируем калькулятор №2. Добавляем тест бизнес-логики через визуальные контролы

Предыдущая серия была тут - http://programmingmindstream.blogspot.ru/2014/02/1-dunit.html

Все исходники будут доступны тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/DraftsAndScketches/SomeTestProjects/DummyCalculator/Chapter2/

 Давайте теперь добавим ОДИН тест "бизнес-логики" в том виде в котором она есть.

Вот он:

Абстрактная часть:

unit CalculatorGUITest;

interface

uses
  TestFrameWork,
  MainForm
  ;

type
  TCalculatorGUITest = class(TTestCase)
   protected
    procedure VisitForm(aForm: TfmMain); virtual; abstract;
   published
    procedure DoIt;
  end;//TCalculatorGUITest

implementation

uses
  Forms
  ;

procedure TCalculatorGUITest.DoIt;
var
 l_Index : Integer;
begin
 for l_Index := 0 to Screen.FormCount do
  if (Screen.Forms[l_Index] Is TfmMain) then
  begin
   VisitForm(Screen.Forms[l_Index] As TfmMain);
   break;
  end;//Screen.Forms[l_Index] Is TfmMain
end;

end.

Конкретный тест:

unit PlusTest;

interface

uses
  CalculatorGUITest,
  MainForm
  ;

type
  TPlusTest = class(TCalculatorGUITest)
   protected
    procedure VisitForm(aForm: TfmMain); override;
  end;//TPlusTest

implementation

uses
  TestFrameWork,
  SysUtils
  ;

procedure TPlusTest.VisitForm(aForm: TfmMain);
const
 aA = 10;
 aB = 20;
begin
 aForm.Edit1.Text := IntToStr(aA);
 aForm.Edit2.Text := IntToStr(aB);
 aForm.Button1.Click;
 Check(StrToInt(aForm.Edit3.Text) = aA + aB);
end;

initialization
 TestFramework.RegisterTest(TPlusTest.Suite);

end.

По-моему тут всй предельно ясно.

Ищем главную форму приложения. Вбиваем текст в Edit1 и Edit2. Нажимаем кнопку сложения. Проверяем результат из Edit3.

Тестируем калькулятор №1. НАСТРАИВАЕМ ИНФРАСТРУКТУРУ. Добавляем DUnit

Предыдущая серия была тут - http://programmingmindstream.blogspot.ru/2014/02/0.html

Теперь попробуем НАСТРОИТЬ ИНФРАСТРУКТУРУ и интегрировать тесты в данное приложение.

Все исходники будут доступны тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/DraftsAndScketches/SomeTestProjects/DummyCalculator/Chapter1/

 Первое, что делаем - это добавляем в проект вот какие строки:

program DummyCalculator;

uses
  Vcl.Forms,
  MainForm in 'MainForm.pas' {fmMain}
  ,
  GUITestRunner // - добавили ссылку на фреймвок тестирования
  ;

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TfmMain, fmMain);
  GUITestRunner.RunRegisteredTestsModeless; // - добавляем форму с тестами
  Application.Run;
end.

Теперь добавляем ПЕРВЫЙ ТЕСТ (пока пустой):

unit FirstTest;

interface

uses
  TestFrameWork
  ;

type
  TFirstTest = class(TTestCase)
   published
    procedure DoIt;
  end;//TFirstTest

implementation

procedure TFirstTest.DoIt;
begin
 Check(true);
end;

initialization
 TestFramework.RegisterTest(TFirstTest.Suite);

end.

Вот что получаем:

Думаю для начала хватит.

В следующем посте я расскажу о тестировании приложения через доступные GUI-контролы.

Тестируем калькулятор №0

"Вводная часть" была тут - http://programmingmindstream.blogspot.ru/2014/02/rumtmarc.html

 Проект калькулятора, который прислал Всеволод Леонов лежит тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/DraftsAndScketches/SomeTestProjects/DummyCalculator/Chapter0/

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

Скажу сразу - пока в указанном проекте никакой "собственной" архитектуры - нет. Ну кроме той, что диктуется библиотекой VCL.

"Просто кнопки", просто "логика на форме".

Это неплохо для "небольших проектов на коленке". Но в какой-то момент с таким подходом становится трудно жить. И надо "что-то менять".

Но! Бросаться СРАЗУ менять - мне кажется неоправданным.

Для начала надо попробовать покрыть хотя бы часть функциональности хотя бы минимальным тестированием.

Т.е. - СНАЧАЛА "хоть какие-то тесты", а ТОЛЬКО потом - "изменение архитектуры".

О тестах данного приложения я напишу в последующем посте.

Ссылка. GoF паттерны на платформе .NET

http://sergeyteplyakov.blogspot.ru/2014/02/gof-net.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed:+blogspot/Znar+(Programming+stuff)

Скажу про себя - я применяю шаблоны не потому, что это "круто" или потому, что их "хочется применять". Я их применяю ТОЛЬКО в случае КРАЙНЕЙ необходимости.

Например НЕОБХОДИМОСТЬ Dependency Injection в ОТДЕЛЬНЫХ случаях я ПРОЧУВСТВОВАЛ.

То же касается и Singleton'а. Опять же в отдельных случаях.

То же касается Publisher/Subscriber и скажем Visitor.

А уж Wrapper, Adapter - БЫВАЮТ крайне полезны при "пересмотре архитектуры". ЧАЩЕ всего как ВРЕМЕННЫЕ меры.

Ну и Fly-Weight - ИНОГДА сильно повышает эффективность.

В общем не шаблоны "ради шаблонов".

А шаблоны, когда они ДЕЙСТВИТЕЛЬНО необходимы.

Ссылка. О фреймворках и свободе

http://sergeyteplyakov.blogspot.ru/2014/01/frameworks-and-freedom.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed:+blogspot/Znar+(Programming+stuff)

"Я помню один довольно забавный случай во время ревью кода, когда довольно простая задача импорта данных из одного источника с последующим экспортом в другой была полностью сокрыта за сложностью кастомного фреймворка. Код должен говорить о своем намерении четко и ясно, а не прятать свою суть за кучей ненужных слоев."

:-)

Я как "писатель фреймворков" под МНОГИМ подпишусь :-)

Коллега прислал скажем так "рецензию"

Исходный пост вот - http://programmingmindstream.blogspot.ru/2014/02/google.html

"Как тестируют в Google".

Вот что пишет коллега (надеюсь он не обидится, то я его цитирую):

"Книжку про тестировании в Google прочитал. Это очень интересно. Даже наше тестирование, казавшееся чуть ли не самым правильным и идеальным в мире, теперь таковым не кажется.
Потому что мы описываем только пользовательские сценарии. А их должно быть примерно 20% от общего кол-ва тестов (70% - системные тесты в коде, 10% - сквозные).
Понятно, что ASSERTS в коде тоже своего рода тесты. Думаю, их даже много. Но работать с ними (то бишь понимать их работу) могут только разработчики.
Ну и от смоук тестирования я бы не отказался. Перед запуском первыми выполнять проверку системных слов и работу основных прецедентов (открыть СР, открыть Предварительный просмотр, открыть документ и тд). По результатам думать - запускать все тесты или какой-то пресет стоит отключить. "

Конец цитаты.

Выделено мной.

Что сказать? Коллегу не буду комментировать. Лишь отмечу, что он ВСЁ ПРАВИЛЬНО пишет и ПРАВИЛЬНЫЕ вопросы поднимает.

От "себя" добавлю лишь вот что:

"У кого-то "не хватает тестов". У меня кстати теперь ДРУГАЯ проблема :) КУДА ДЕВАТЬ СТОЛЬКО ТЕСТОВ :)
есть комплекты которые по два часа идут :( и оптимизировать уже не получается

раньше я перед коммитами прогонял РЕАЛЬНО ВСЕ тесты.. а теперь уже выделил подмножества.. так скажем "дымовых"..."

Маленькая ремарка о "тестах" и "ошибках"

По мотивам - http://programmingmindstream.blogspot.ru/2014/02/blog-post_15.html

Почему я пишу вот это:

"Далее ПРОЩЕ ВСЕГО "оттолкнуться" от какой-нибудь ОШИБКИ, которую вы СЕЙЧАС СОБРАЛИСЬ править.

Желательно НЕ ОЧЕНЬ СЛОЖНОЙ.

Повторю! ПРОЩЕ ВСЕГО "оттолкнуться" от какой-нибудь ОШИБКИ, которую вы СЕЙЧАС СОБРАЛИСЬ править.

Можно писать тесты и по ТЗ и по НОВОЙ функциональности, но ОТТАЛКИВАТЬСЯ от УЖЕ найденных ошибок - ПРОЩЕ ВСЕГО."

Действительно ПОЧЕМУ?

Дело в регрессионном тестировании (http://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%80%D0%B5%D1%81%D1%81%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B5_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)?

СОВСЕМ НЕТ!

Дело не в этом.

Дело в ТОМ, что ТЗ зачастую НЕПОЛНО (иногда его ПРОСТО НЕТ) и не содержит ВСЕ необходимые ДЕТАЛИ.

Что тестировать? Как тестировать? Что правильно? Что неправильно?

В ТЗ (ЗАЧАСТУЮ) это НЕ УКАЗАНО.

Если у вас ДРУГАЯ СИТУАЦИЯ - вам повезло :-) но я лично в жизни "другой ситуации" - не видел.

С той или иной степенью допуска.

Всё потому, что ТЗ пишут люди "которым и ТАК ВСЁ понятно" (тут перекликается с - http://programmingmindstream.blogspot.ru/2014/01/blog-post_9265.html).

Это не их ВИНА, это их БЕДА! Я САМ - ТОЧНО такой же.

Мне самому - "всё понятно". Пока не начинаешь пытаться ДОНЕСТИ свои мысли до других.

Итак!

ТЗ АПРИОРИ (по моему опыту) - НЕ ПОЛНО.

И дело не в ПИСАТЕЛЯХ ТЗ, дело в КОММУНИКАЦИЯХ (тут можно вспомнить вот что - http://programmingmindstream.blogspot.ru/2014/01/blog-post_9265.html).

Никто не САБОТИРУЕТ написание ТЗ. Просто ВСЕ ЛЮДИ РАЗНЫЕ и все ПО-РАЗНОМУ понимают "один и тот же текст".

ОСОБЕННО - "писатель текста" и "читатель текста".

Да! "Со временем" всё шлифуется и налаживается.

Но! Почему ошибки?

ОШИБКИ - это СОВСЕМ ДРУГОЕ дело.

В ошибках ОБЫЧНО ЧЁТКО написано:

1. Что делали.
2. Что ожидали получить.
3. Что получили на самом деле.

Поэтому ОШИБКИ это источник ХОРОШИХ тестов.

В ошибках (ОБЫЧНО) всё предельно понятно.

Повторю:
1. Что делали.
2. Что ожидали получить.
3. Что получили на самом деле.

Посему - ДЛЯ НАПИСАНИЯ ТЕСТОВ - проще всего ОТТАЛКИВАТЬСЯ от УЖЕ НАЙДЕННЫХ ОШИБОК.

Мысль понятна?

Подводим "промежуточные" итоги о тестировании

Есть два поста:

"Всеволод Леонов задал правильные вопросы о тестировании" - http://programmingmindstream.blogspot.ru/2014/02/blog-post_15.html
"Как тестировать "вообще нетестируемое"" - http://programmingmindstream.blogspot.ru/2014/02/blog-post_4473.html

Они друг другу "несколько противоречат".

О чём вообще речь?

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

Почему так?

Скажем так - ПЕРВЫЙ путь он - БОЛЕЕ ПРАВИЛЬНЫЙ (с моей точки зрения).
А ВТОРОЙ - менее затратный.

Почему я предлагаю их оба?

Потому, что на самом деле я ОБА эти пути прошёл.

Поясню.

"Пилить архитектуру" и делать приложение "более тестируемым" - это конечно ХОРОШО.

Но! ЕСЛИ ТЕСТОВ нет, то ГДЕ ГАРАНТИЯ, что при "распиле архитектуры ничего не сломается"?

"Ручное протыкивание"?

ВАРИАНТ, но я обычно в него слабо верю. Особенно если "протыкивать" приходится МНЕ.

Я СЕБЕ - НЕ ДОВЕРЯЮ.

Что же делать?

Вот тут на сцену выходит ВТОРОЙ ВАРИАНТ.

Мы СНАЧАЛА - НИЧЕГО "не пилим", не трогаем АРХИТЕКТУРУ приложения. Ну "вся логика на формах", ну "Button.Click" - ПУСТЬ ТАК.

НИЧЕГО ПОКА НЕ ТРОГАЕМ.

ВНЕДРЯЕМ тесты в СУЩЕСТВУЮЩЕЕ ПРИЛОЖЕНИЕ.

И пишем их ТАК как СУЩЕСТВУЮЩЕЕ ПРИЛОЖЕНИЕ нам "диктует".

И пишем их ТАК как написано во ВТОРОМ посте. Вызывая Button.Click и считывая данные из контролов форм.

Это скажем так "наши контрольные точки". Отправные.

Итак.

Пишем тесты в ТАКОМ СТИЛЕ и убеждаемся, что ОНИ ПРОХОДЯТ.

Вторым пунктом - УБЕЖДАЕМСЯ, что тесты "хоть что-то проверяют".

При этом параллельно продолжаем нашу разработку.

И "время от времени" убеждаемся, что тесты "таки находят ошибки".

Это конечно НИЧТО НЕ ГАРАНТИРУЕТ. Но это "само по себе" - ПРИЯТНО.

Далее продолжаем писать ТЕСТЫ В ТАКОМ стиле и набираем их "некоторую критическую массу".

Которая "по нашим ощущения" говорит нам о том, что "многое из основных потоков" - ПРОТЕСТИРОВАНО.

Точнее - "покрыто тестами".

КОНЕЧНО это - "оценка на глазок".

Вот! Когда мы достигнем этой "критической массы" - можно переходить ко второй фазе.

Писать тесты уже ТРАНСФОРМИРУЯ архитектуру.

Т.е. под НОВЫЕ тесты АДАПТИРОВАТЬ АРХИТЕКТУРУ приложения, чтобы её было УДОБНО тестировать.

Применяя всяческие "критерии" (http://programmingmindstream.blogspot.ru/2014/02/blog-post_5990.html) и "паттерны" (типа Dependency Injection).

При этом ВАЖНО! После КАЖДОЙ ТРАНСФОРМАЦИИ архитектуры запускать ВЕСЬ КОМПЛЕКТ ТЕСТОВ.

При это ВАЖНО вот ещё что - надо ВАЛИДИРОВАТЬ те самые "первые тесты", которые мы написали БЕЗ ТРАНСФОРМАЦИИ архитектуры.

На предмет чего?

На предмет того - а не ВОЗМОЖНО ли их переписать уже с учётом СЛУЧИВШЕЙСЯ ТРАНСФОРМАЦИИ архитектуры, но БЕЗ УЩЕРБА качеству.

Если ВОЗМОЖНО, то их следует ПЕРЕПИСЫВАТЬ.

ДАЖЕ ТАК - на какой-то момент времени иметь ДВА НАБОРА таких тестов - "переисанные" и "изначальные".

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

И?!

В КАКОЙ-ТО момент мы избавимся от "изначальных" тестов.

Которые дали нам "гарантию" от ошибок на "точке старта".

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

Получим систему с ОТРИЦАТЕЛЬНОЙ обратной связью (http://ru.wikipedia.org/wiki/%D0%9E%D1%82%D1%80%D0%B8%D1%86%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%B0%D1%8F_%D1%81%D0%B2%D1%8F%D0%B7%D1%8C).

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

Система будет не РАСКАЧИВАТЬСЯ (как упомянуто тут - http://18delphi.blogspot.ru/2013/03/blog-post.html), а СТАБИЛИЗИРОВАТЬСЯ.

Ошибки в тестах вылезут в РЕАЛЬНОМ КОДЕ, а ошибки в коде - вылезут в тестах.

И в этот момент можно будет сказать - "да наша система - ТЕСТИРУЕМА". (Понятное дело, что "на столько-то процентов").

Мысль понятна?

1. СНАЧАЛА пишем "хоть какие-то тесты", которые "хоть что-то проверяют".
2. Убеждаемся, что они НИЧЕГО НЕ СЛОМАЛИ (пусть и ручным протыкиванием). Этот шаг - ОЧЕНЬ ВАЖЕН.
3. УБЕЖДАЕМСЯ, что тесты "проверяют то, что нужно".
4. Набираем "критическую массу" таких тестов.
5. Начинаем писать "новые" тесты, "подтачивая архитектуру".
6. При этом проверяем ВЕСЬ комплект тестов.
7. Время от времени пишем замену "изначальным" тестам.
8. Время от времени удаляем "изначальные" тесты для которых есть замена.
9. В какой-то момент "изначальных" тестов - НЕ ОСТАЁТСЯ.
10. Получаем систему с ОТРИЦАТЕЛЬНОЙ ОБРАТНОЙ СВЯЗЬЮ.

И далее рекурсия - "тесты влияют на архитектуру (и код)", а "архитектура (и код) - влияют на тесты".

Ну и далее можно пойти "на новый виток" - тесты влияют на код, тесты влияют на ТЗ, ТЗ влияет на тесты и ТЗ влияет на код.

Но это уже "в следующих сериях".

Как "применить всё это на практике"?

В БЛИЖАЙШЕЕ время я думаю, что я смогу начать публиковать код - http://programmingmindstream.blogspot.ru/2014/02/rumtmarc.html

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

вторник, 18 февраля 2014 г.

Как тестировать модальные диалоги?

По мотивам - http://programmingmindstream.blogspot.ru/2014/02/blog-post_4473.html

Button.Click это конечно - "хорошо", но есть "шанс" нарваться на "модальные диалоги", которые "берут управление на себя".

Методы борьбы с этим я "придумал", через перекрытие ShowModal И внедрение в него "контекста входа" и "контекста выхода" и "эмуляцию возврата" ModalResult.

Рассказать как?

Неожиданное "вознобновление" темы RUMTMARC

Исходный пост был тут - http://18delphi.blogspot.ru/2013/04/0-rup-uml-mda-etc-al.html

Но - "руки не дошли".

Потому что у меня - "нехватка времени", а у аудитории - "нехватка интереса".

Но!

Тут Всеволод Леонов неожиданно возродил тему - http://programmingmindstream.blogspot.ru/2014/02/anemicdomainmodel.html?showComment=1392717297690#c4055365633171954826

На что я ответил:

http://programmingmindstream.blogspot.ru/2014/02/blog-post_4473.html

"P.P.P.P.S. Вот тут - http://programmingmindstream.blogspot.ru/2014/02/anemicdomainmodel.html?showComment=1392717297690#c4055365633171954826 - мне "бросили вызов" :-) Я его - ПРИНИМАЮ :-)

Всеволод прислал мне проект, который предстоит протестировать.

Сам проект пока лежит вот тут - https://dl.dropboxusercontent.com/u/60632578/DummyCalculator.zip тесты же к нему я буду публиковать в блоге."

Так что - "следите за обновлениями".. :-)

Начнём с "малого" :-) с тестов. А там глядишь и до архитектуры и до (чем чёрт не шутит) UML и кодогенерации.

А уж если мы дойдём до ТЗ :-)

Ссылка. Роман Янковский. Тестируемая архитектура

http://roman.yankovsky.me/?p=1541

Роман ПРАВИЛЬНО всё написал.

Замечу лишь, что это ОТНЮДЬ НЕ ПРОТИВОРЕЧИТ "постулату тестируемости".

Роман дал "критерии" оценки "хорошести" архитектуры.

А "тестируемость" - даёт способы ОЦЕНКИ этих критериев.

Другими словами - рано или поздно - через "тестируемость" вы придёте к данным "критериям".

А от этих "критериев" - скорее всего придёте к "тестируемости".

Просто (как мне кажется) - Роман сразу пытается больше "теорию" развивать, а я - "практику".

На теорию - у меня мозгов не хватает. Я могу лишь на "прецеденты использования" смотреть, а лишь ПОТОМ их обобщать.

А вообще говоря - Роман - ОЧЕНЬ ХОРОШИЕ и ПРАВИЛЬНЫЕ мысли озвучил.

Пояснение. О "ссылках на самого себя"

Как например тут - http://programmingmindstream.blogspot.ru/2014/02/blog-post_18.html

Поясню - "зачем" и "почему" я их даю.

"Оказывается", что люди не читали посты, которые я писал уже почти год назад.

Ну или - "пропустили".

Почему "оказывается"?

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

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

Как тестировать "вообще нетестируемое"

Вот тут - http://programmingmindstream.blogspot.ru/2014/02/blog-post_15.html я попытался расписать "алгоритм" тестирования "нетестируемой" архитектуры и то как привести её к "тестируемой".

Давайте зайдём с другой стороны.

Пусть у нас есть "в принципе нетестируемое приложение".

Давайте рассмотрим "САМЫЙ УЖАСНЫЙ" случай.

Всё делается в "обработчике" OnClick на ФОРМЕ и при этом ОБРАБАТЫВАЮТСЯ ДАННЫЕ из БД.

А именно:

Оно - МОНОЛИТНОЕ и работающее с DataSet'ами на форме.

Примерно так:

type
 TMainForm = class(TForm)
  DataSet: TDataSet;
  Grid: TGrid;
  Button: TButton;
  Label: TLabel;
 end;//TMainForm

procedure TMainForm.ButtonClick(aSender: TObject);
begin
 Label.Caption := Grid.SomeRow.SomeCalculations;
end;
...
begin
 Application.CreateForm(TMainForm, MainForm);
 Application.Run;
end.

Давайте протестируем его:

Немного изменим код проекта:

...
begin
 Application.CreateForm(TMainForm, MainForm);
 GUITestRunner.TGuiTestRunnerForm.Create(Application); 
 // - тут мы вставили окно с "запускалкой тестов"
 Application.Run;
end.

Добавим тест:

unit MyTest;

interface

uses
 TestFramework;

type
 TMyTest = class(TTestCase)
  published
   procedure DoIt;
 end;//TMyTest

implementation

uses
 MainForm // - ДА ТУТ мы ЗНАЕМ про ГЛАВНУЮ форму приложения
 ;

procedure TMyTest.DoIt;
var
 l_Form : TForm;
begin
 for l_Form in Screen.ActiveForms do
  if (l_Form Is TMainForm) then
  begin
   TMainForm(l_Form).Button.Click;
   l_Text := TMainForm(l_Form).Label.Caption;
   CompareWithEtalon(l_Text);
   break;
  end;//l_Form Is TMainForm
end;

initialization
 TestFramework.RegisterTest(TMyTest);

Мысль понятна?

Как писать CompareWithEtalon?

Как найти "ту самую" TMainForm?

Как обеспечить "повторяемость" теста?

Как обеспечить "состояние базы"?

Это - ВОПРОСЫ - ДА.

Они - ОБСУЖДАЕМЫ!

Но!

Приложение ведь ПЕРЕСТАЛО быть "нетестируемым"?

Не так ли?

ВОПРОСЫ - ЕСТЬ.

Много вопросов.

Но!

Они - обсуждаемы.

По "ходу пьесы".

Но они "обсуждаемы" уже не АБСТРАКТНО - "а что делать когда всё плохо", а вполне - КОНКРЕТНО, в виде - "а что делать если есть ТАКАЯ КОНКРЕТНАЯ проблема".

Надеюсь, что я обрисовал ещё "один из путей" - как тестировать "нетестируемое приложение".

И как "оторваться от "коня в вакууме"".

P.S. Понятное дело, что "это" самый что ни на есть "белый ящик". Но надо же С ЧЕГО-ТО НАЧИНАТЬ! За неимением "горничной"...

P.P.S. Ну и понятное дело, что ТУТ речь не идёт ни о "GUI-тестах", ни о "комплексных" тестах, ни ТЕМ БОЛЕЕ об "атомарных". Тут речь идёт ЛИШЬ о том, как написать "хоть какой-нибудь" тест. Типа - "с чего начать". Как ИНФРАСТРУТУРУ "выращивать".

Напишете десяток таких "кривых, косых и непонятных" тестов - дальше "дело пойдёт легче". Я ПРАКТИЧЕСКИ УВЕРЕН в этом. :-)

Дальше начнёте рефакторить и тесты и код. Избавляясь от "кривизны" и "Cut'n'Paste".

И дело "само пойдёт".

И со временем будет "правильные" тесты:
1. "Атомарные".
2. "Комплексные".
3. Регрессионные.
4. GUI.

А таких "кривых и косых" - НЕ ОСТАНЕТСЯ.

ГЛАВНОЕ - НАЧАТЬ!

Хоть "с чего-нибудь".

P.P.P.S. Понятное дело, что САМОЕ СЛОЖНОЕ если по ButtonClick "вдруг" поднимется МОДАЛЬНЫЙ ДИАЛОГ. Это тоже - ВОПРОС. Который МОЖНО и НУЖНО обсуждать.

Но это уже - ВОПРОС, а не "конь в вукууме".

P.P.P.P.S. Вот тут - http://programmingmindstream.blogspot.ru/2014/02/anemicdomainmodel.html?showComment=1392717297690#c4055365633171954826 - мне "бросили вызов" :-) Я его - ПРИНИМАЮ :-)

Всеволод прислал мне проект, который предстоит протестировать.

Сам проект пока лежит вот тут - https://dl.dropboxusercontent.com/u/60632578/DummyCalculator.zip тесты же к нему я буду публиковать в блоге.

Ссылка. Инверсия управления

http://ru.wikipedia.org/wiki/IoC

Цитата:

"Инверсия управления (англ. Inversion of Control, IoC) — важный принцип объектно-ориентированного программирования, используемый для уменьшения связанности в компьютерных программах.
Формулировка:
  • Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Одной из реализаций IoC является внедрение зависимостей (англ. dependency injection). Внедрение зависимости используется во многих фреймворках, которые называются IoC-контейнерами.
Если сравнить с более низкоуровневыми технологиями, IoC-контейнер - это компоновщик, который собирает не объектные файлы, а объекты ООП (экземпляры класса) во время исполнения программы. Очевидно, для реализации подобной идеи было необходимо создать не только сам компоновщик, но и фабрику, производящую объекты. Аналогом такого компоновщика (естественно, более функциональным) является компилятор, одной из функций которого является создание объектных файлов. В идее компоновки программы во время исполнения нет ничего нового. Предоставление программисту инструментов внедрения зависимостей дало значительно бо́льшую гибкость в разработке и удобство в тестировании кода.[источник не указан 96 дней]

Техники реализации[править | править исходный текст]

"

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

http://programmingmindstream.blogspot.ru/2014/02/blog-post_5.html?showComment=1392488674148#c3281074560201731492

Ещё ссылка. Ну и ещё одна хорошая статья про TDD

http://18delphi.blogspot.ru/2013/05/tdd.html

Цитата из оригинальной статьи:

"Как-то в одной из TDD-рассылок я наткнулся на некую классификацию способов применения модульных тестов разработчиками:
  1. Традиционный — когда разработка ведется полностью через тесты, один тест за раз, при этом активно применяется рефакторинг. Первоначальная реализация рабочего кода обычно является нарочно упрощенной, конечный дизайн кода получается последовательно и только исходя из появления новых тестов
  2. Активный — отличается от традиционного тем, что разработчик сначала обдумывает дизайн рабочего кода, а затем начинает целенаправленно идти к этому дизайну через тесты.
  3. Приемочный — вместо того, чтобы писать небольшие тесты, разработчик пишет сразу конечный тест, который реализует конечную функциональность. Далее он продумывает дизайн реализации, и всю несуществующую функциональность забивает мок-объектами, стараясь запустить тесты как можно раньше. После этого постепенно убирает мок-объекты, заменяя их реальными классами.
Только первый вариант можно считать полноценным TDD. Второй вариант — это так называемая методика test-first development, то есть TDD без первой D — driven. Третий вариант никакого отношения к самому TDD не имеет, а должен применяться параллельно в виде приемочных тестов.
 TDD — это процесс итеративного, непрерывного, параллельного написания тестов и рабочего кода, с обязательными фазами рефакторинга.
Очень многие разработчики начинают сразу с набора модульных тестов для нового класса, а лишь только затем пишут сам класс. В итоге они тратят полдня на то, чтобы эти тесты сработали. Им приходится отлаживать не только рабочий код, но и тесты, при этом любое изменение структуры заставляет их переписывать большие участки тестового кода — это нудно, неинтересно и занимает много времени. Это вовсе не TDD и даже не test-first. Когда вы занимаетесь TDD, вы должны стремиться минимизировать размер контекста, в котором вы работаете. Это относится как к тесту, так и к рабочему коду. Мы обычно пишем тесты очень мелкими шагами и не пытаемся представить, как будет выглядеть окончательный код. Это позволяет очень рано увидеть зеленую линию и обойтись без debug сессии при начальном запуске тестов. Даже если и так понятно, как будет выглядеть все остальные тесты и сам рабочий код класса, мы предпочитаем написать короткий тест, затем небольшую часть рабочего кода. Затем можно двигаться быстрее и увереннее. Конечно, каждый самостоятельно может определять, что для него «небольшие шаги», но общая рекомендация такая — старайтесь сокращать промежутки между запусками тестов. При активной разработке мы обычно запускаем тесты раз в 2-3 минуты и даже чаще. Это позволяет проверять написанный код сразу же, не теряя контекста, то есть, помня все детали только что измененного кода.
Рассмотрим «активный» и «приемочный» способы использования тестов (см. выше). Мы начинали свою практику TDD именно с «активного» способа применения модульных тестов, рисуя UML-диаграммы, а затем старались при помощи тестов реализовать свои задумки в коде. Но постепенно мы отказались от повседневного применения этой практики. Дело в том, что если разрабатывать, ставя во главу test-ability, то есть возможность протестировать код, то полученная реализация всегда будет отличаться от задуманной, а времени, затраченного на детальную проработку дизайна, становится жаль. Теперь мы стараемся не тратить больше 10-15 минут на дизайн-сессии, достаточно пары небольших набросков на доске, которые дадут ориентировочную картину, и можно приступать к кодированию. Кстати, я вовсе не хочу сказать, что TDD заменяет фазу анализа проекта. Архитектурные решения, оказывающие влияние на весь проект в целом, все равно нужно принимать. В конце концов, TDD никак не может заменить аналитические способности разработчика, его умение предугадывать развитие проекта. Но TDD реально позволяют выбраться из ситуации архитектурного тупика (design deadlock), когда вообще непонятно, как должна выглядеть реализация."

Ссылка. Anemic_domain_model

http://en.wikipedia.org/wiki/Anemic_domain_model

"Anemic domain model is the use of a software domain model where the domain objects contain little or no business logic (validations, calculations, business rules etc)."

Вот тут опять мне "раскрыли глаза".

Что сказать. Удивлён!

Не знал, что ЕСТЬ ПОДОБНЫЙ термин.

Почему?

Потому, что (не устаю это повторять) - "САМ ПОДОБНОЕ использую".

В ЧАСТНОСТИ в текстовом редакторе (процессоре).

В НЁМ чётко разделены "объекты данных" и "бизнес объекты". Объекты данных - ЛИШЬ ХРАНЯТ данные (и обладают СОСТОЯНИЕМ), а бизнес объекты их ОБРАБАТЫВАЮТ (и состоянием НЕ ОБЛАДАЮТ).

И это было сделано ОСОЗНАННО.

Чтобы РАЗДЕЛИТЬ данные и бизнес-логику.

Простейший пример - TextPara - объект хранящий данные текстового параграфа.

И целая вязанка "бизнес-объектов":

1. TextParaCursor.
2. TextParaSelection.
3. TextParaPainter.
4. TextParaFormatInfo.
5. TextParaHotSpot.
6. TextParaMarkers.
etc.

Аналогично - Table - объект хранящий данные таблицы.

И целая вязанка "бизнес-объектов":

1. TableCursor.
2. TableSelection.
3. TablePainter.
4. TableFormatInfo.
5. TableHotSpot.
6. TableMarkers.
etc.

При этом "бизнес-объекты" связываются с "объектами данных", через Dependency Injection. То есть имея созданный и заполненный "объект данных" можно всегда "опосредованно" (не приведением классов и не "прямым знанием", а через "фабрику") получить интересующий "бизнес-объект".

Который НЕ ОБЛАДАЕТ "хранимым" состоянием и НЕ ВЛИЯЕТ на состояние "объекта данных" (это - ВАЖНО!).

"Бизнес-объект", если и обладает "состоянием", то она НЕ ВЛИЯЕТ на остальные участки системы, оно лишь "кешируется" в ДАННОЙ КОНКРЕТНОЙ копии "бизнес-объекта".

Если "соседний код", получит ДРУГОЙ "бизнес-объект" средствами той же фабрики - эти два "бизнес-объекта" НИКОИМ образом не будут связаны и НЕ БУДУТ влиять ДРУГ НА ДРУГА.

Говорят - "анти-паттерн" от Фаулера (http://www.martinfowler.com/bliki/AnemicDomainModel.html) :-) Ну что же... Я "похоже люблю анти-паттерны".. В некотором роде.

Вот тут кстати про это немного написано:

http://18delphi.blogspot.ru/2013/04/blog-post_10.html
http://18delphi.blogspot.ru/2013/04/blog-post_8517.html
http://18delphi.blogspot.ru/2013/06/offtopic.html

И не могу НЕ ОТМЕТИТЬ CoreText - там есть "подобная схема":

NSAttributedString - "объект данных"
CTFrameSetter - "бизнес-объект" работающий с NSAttributedString и порождающий ДРУГОЙ "объект данных" - CTFrame.

"The CTFramesetter opaque type is used to generate text frames. That is, CTFramesetter is an object factory for CTFrame objects.


The framesetter takes an attributed string object and a shape descriptor object and calls into the typesetter to create line objects that fill that shape. The output is a frame object containing an array of lines. The frame can then draw itself directly into the current graphic context."

Подробнее тут - https://developer.apple.com/library/mac/documentation/Carbon/reference/CTFramesetterRef/Reference/reference.html

P.S. Правда сегодня мне коллега УКАЗАЛ на то, что я "не совсем верно трактую Фаулера". :-) ВПОЛНЕ МОЖЕТ БЫТЬ. Жаль мы не успели с ним детали обсудить. Обсудим - ОБЯЗАТЕЛЬНО напишу об ИТОГАХ.