четверг, 7 ноября 2013 г.

Offtopic. Прислали отзыв. Насчёт "GUI-тестирования "по-русски""

Вот он:

"Лучше, чем в любом учебнике по программированию! 

Мне очень нравится такой стиль.

http://18delphi.blogspot.com/2013/11/gui_9015.html"

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

Ссылка. "Синтаксический сахар" конечно. Но прикольно

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

Вот тут - http://18delphi.blogspot.ru/2013/11/gui-3.html я писал про слово "между".

А что делать если кто-то захочет вызвать его вот так:

2 между ( 2 5 10 )

?

Компилятор это - спокойно "переварит", но код будет выполняться с ошибками.

Да ещё и на стеке значений будут накапливаться "паразитные значения".

Как этого избежать?

На этот случай у нас есть "специальные" слова "для контроля за стеком значений".

Слова :

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

Как это применяется?

А вот так:

BOOLEAN WORDWORKERFUNCTION "строго между" INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"

 #BeginControlStack // - начинаем контроль стека
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 #EndControlStack ( 2 == ) // - заканчиваем контроль стека и убеждаемся, что на стек попало РОВНО 2 значения

 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 ( anX больше "Нижняя граница" ) И ( anX меньше "Верхняя граница" ) =: Result
END // "строго между"

Тогда код:

2 между ( 2 5 )

- нормально выполнится.

 А код:

2 между ( 2 5 10 )

- поднимет ошибку.

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

Но это - отдельная тема.

Об этом я ОБЯЗАТЕЛЬНО напишу - но чуть позже.

GUI-тестирование "по-русски". Скажем так. "Анонс существующих возможностей" №3

Вот тут - http://18delphi.blogspot.ru/2013/11/gui-2_7.html я писал про WORDWORKER, WORDWORKERFUNCTION и слова "между" и "строго между".

Но я ещё обещал рассказать про логические операторы.

Сейчас расскажу.

Итак.

У нас было слово "между".

Выглядело оно вот так:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 anX "Нижняя граница" >= IF (
  X "Нижняя граница" <= IF
   true =: Result
  ELSE
   false =: Result
 )
 ELSE
  false =: Result
END //между

Два вложенных IF. Нормально.

Но как же логические операторы?

Они у нас есть.

На "хардкорном" уровне их использование выглядит так:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 anX "Нижняя граница" >= 
 anX "Верхняя граница" <= && IF
  true =: Result
 ELSE
  false =: Result
END //между

От одного IF - избавились.

Но по-моему - всё равно - "ужасно".

Обратная польская запись.

Будем исправлять ситуацию.

Введём слово:

BOOLEAN WORDWORKERFUNCTION И BOOLEAN IN aLeft
 aLeft IF
  WordToWork DO IF
   true =: Result
  ELSE
   false =: Result
 ELSE
  false =: Result
END // И

Мы ввели оператор "И" - мало того, что он ИНФИКСНЫЙ, так он ещё и ЛЕНИВЫЙ.

Т.е. он может не вычислять ВСЁ ВЫРАЖЕНИЕ, если уже понятен результат.

Тогда наш пример переписывается так:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 ( anX "Нижняя граница" >= ) И ( anX "Верхняя граница" <= ) IF
  true =: Result
 ELSE
  false =: Result
END //между

Поработаем ещё.

Введём слова:

BOOLEAN WORDWORKERFUNCTION "меньше или равно" INTEGER IN aLeft
 aLeft WordToWork DO <= =: Result
END // "меньше или равно"

BOOLEAN WORDWORKERFUNCTION "больше или равно" INTEGER IN aLeft
 aLeft WordToWork DO >= =: Result
END // "больше или равно"

Тогда наш пример можно переписать вот так:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 ( anX "больше или равно" "Нижняя граница" ) И ( anX "меньше или равно" "Верхняя граница" ) IF
  true =: Result
 ELSE
  false =: Result
END // между

Ну или в конечном итоге:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 ( anX "больше или равно" "Нижняя граница" ) И ( anX "меньше или равно" "Верхняя граница" ) =: Result
END // между

Оператор IF - нам стал НЕ НУЖЕН. Он - "скрыт" во вспомогательных словах.

Аналогично:

BOOLEAN WORDWORKERFUNCTION меньше INTEGER IN aLeft
 aLeft WordToWork DO < =: Result
END // меньше 

BOOLEAN WORDWORKERFUNCTION больше INTEGER IN aLeft
 aLeft WordToWork DO > =: Result
END // больше 

И:

BOOLEAN WORDWORKERFUNCTION "строго между" INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 ( anX больше "Нижняя граница" ) И ( anX меньше "Верхняя граница" ) =: Result
END // "строго между"

Вот как-то так...

Понятное дело, что всё это относится и к операторам || и != и т.п. и т.п.

Позже - я ОБЯЗАТЕЛЬНО расскажу - "как это устроено внутри".

P.S. Хочу ещё "краем коснуться" вот этого - "Оператор IF - нам стал НЕ НУЖЕН. Он - "скрыт" во вспомогательных словах." - это - ВАЖНО. По моему ГЛУБОКОМУ убеждению - высокоуровневые тесты - ДОЛЖНЫ быть избавлены от управляющих конструкций типа IF, WHILE, LOOP, TRY и т.д. и т.п.

Они должны представлять собой лишь НАБОР ПОСЛЕДОВАТЕЛЬНЫХ действий. Которые либо проходят, либо - не проходят.

Это - ВАЖНО. НА САМОМ деле - ВАЖНО.

А управляющие конструкции ДОЛЖНЫ БЫТЬ "упакованы" во "вспомогательные слова" словарей.

Ещё раз. ЭТО - ВАЖНО.

Для того, чтобы тесты были понятны и ДЕТЕРМИНИРОВАНЫ.

GUI-тестирование "по-русски". Скажем так. "Анонс существующих возможностей" №2

Эта статья опять будет без отсылок к Delphi.

Да простят меня Borland и Embarcadero.

 Вот тут - http://18delphi.blogspot.ru/2013/11/gui_9015.html я описал использование слова WORDWORKER.

На самом деле - использование этого слова - позволяет ЗНАЧИТЕЛЬНО влиять на грамматику языка.

Это слово и подобные ему позволяют формировать "свой DSL" из аксиоматики заданной в коде скриптовой машины на Delphi (всё же одна отсылка есть)

 Приведу ещё один пример.

 Давайте узнаем входит ли число в заданный интервал.

Например между 2 и 10.

 В нашем языке конечно есть конструкция if и всякие разные операторы сравнения. Как они "устроены внутри" - я КОНЕЧНО ЖЕ позже расскажу.

А пока - вернёмся к нашей задаче.

 На "хардкорном" уровне поставленная выше задача решается так:

X 2 >= IF (
 X 10 <= IF
  true
 ELSE
  false
)
ELSE
 false

По-русски? Да что-то - СОВСЕМ не по-русски. Да и "обратная польская запись". Не все её любят.

 Поработаем над этим.

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

WORDWORKERFUNCTION отличается от WORDWORKER тем, что не только умеет работать с "параметрами слева" или "параметрами справа", но и возвращать значение.

Попробуем описать слова "между" и "строго между".

Выглядит это так:

BOOLEAN WORDWORKERFUNCTION между INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 anX "Нижняя граница" >= IF (
  anX "Верхняя граница" <= IF
   true =: Result
  ELSE
   false =: Result
 )
 ELSE
  false =: Result
END //между

BOOLEAN WORDWORKERFUNCTION "строго между" INTEGER IN anX
 INTEGER VAR "Нижняя граница"
 INTEGER VAR "Верхняя граница"
 WordToWork // - слово справа, а на самом деле может быть "последовательность слов", за счёт оператора "скобки" (о нём я расскажу позже)
 DO // - вычисляем переданное слово
 =: "Верхняя граница" // - снимаем со стека верхнюю границу
 =: "Нижняя граница" // - снимаем со стека нижнюю границу
 anX "Нижняя граница" > IF (
  anX "Верхняя граница" < IF
   true =: Result
  ELSE
   false =: Result
 )
 ELSE
  false =: Result
END //между

Пример использования:

 
 3 между ( 2 5 ) // true
 0 между ( 2 5 ) // false
 2 между ( 2 5 ) // true
 5 между ( 2 5 ) // true
 10 между ( 2 5 ) // false

 3 "строго между" ( 2 5 ) // true
 2 "строго между" ( 2 5 ) // false
 5 "строго между" ( 2 5 ) // false
 0 "строго между" ( 2 5 ) // false
 10 "строго между" ( 2 5 ) // false

Ну и ещё есть "синтаксический сахар", что а_без_пробелов равно "а_без_пробелов" поэтому для "единообразия можно писать и так:

 3 "между" ( 2 5 ) // true
 0 "между" ( 2 5 ) // false
 2 "между" ( 2 5 ) // true
 5 "между" ( 2 5 ) // true
 10 "между" ( 2 5 ) // false

 3 "строго между" ( 2 5 ) // true
 2 "строго между" ( 2 5 ) // false
 5 "строго между" ( 2 5 ) // false
 0 "строго между" ( 2 5 ) // false
 10 "строго между" ( 2 5 ) // false

Ещё хотел написать о логических операторах.

Но по-моему - "и так много".

В общем - в следующей серии".

Сейчас я её напишу.

Следующая серия вот - http://18delphi.blogspot.com/2013/11/gui-3.html

среда, 6 ноября 2013 г.

GUI-тестирование "по-русски". Скажем так. "Анонс существующих возможностей"

Вот тут - http://18delphi.blogspot.ru/2013/11/gui_4.html я описал слово "Нажать". Которое позволяет нажимать клавиши и отправлять их текущему контролу.

Используется это слово так:

"Нажать {('Enter')}"
"Нажать {('Tab')}"
"Нажать {('Shift-Tab')}"
"Нажать {('Down')}"

А если надо нажать скажем 5 раз?

 Ну можно написать так:
"Нажать {('Down')}"
"Нажать {('Down')}"
"Нажать {('Down')}"
"Нажать {('Down')}"
"Нажать {('Down')}"

А если 100? А тысячу?

 На самом деле у нас в языке конечно есть циклы. Разные.

 Один из них - LOOP.

 Его использование выглядит так:

 5 LOOP "Нажать {('Down')}"

Этот пример нажимает клавишу "стрелка вниз" 5 раз. 

Кстати давайте его сразу перепишем:

Введём в словарь определение:

CONST "Стрелка вниз" 'Down'

Тогда пример можно переписать так:

 5 LOOP "Нажать {("Стрелка вниз")}"

По-русски? Да вроде бы - да, но не совсем.

Как это переписать "по-русски"?

До сих пор нам встречались определения новых слов такие как PROCEDURE и FUNCTION.

Они умеют работать с "параметрами слева", т.е. с теми значениями, которые помещены на стек значений.

Но. Есть и ещё одно слово для определения НОВЫХ пользовательских слов.

Называется - WORDWORKER.

Оно умеет работать не только с "параметрами слева", но и с "параметрами справа". Давайте посмотрим на пример его использования.

Напишем:
 WORDWORKER раз INTEGER IN aCount // - aCount это "параметр слева"
  aCount LOOP // - LOOP - уже знакомый нам оператор цикла
  ( 
   WordToWork // - WordToWork - "параметр справа", т.е. ссылка на то, что идёт за нашим словом
   DO // - вычисляет значение WordToWork. В терминах паскаля DO "эквивалентен" операции ^, или в терминах C - *
  )
 ; // раз 

Теперь наш пример можно написать так:

 5 раз "Нажать {("Стрелка вниз")}"

А если нам надо нажать 2 раза? Но пока это выглядит так:

 2 раз "Нажать {("Стрелка вниз")}"

По-русски? Вроде - да.

Но не совсем. Коряво очень. "По-колхозному".

Что можно сделать?

Можно конечно определить слово "раза", через WORDWORKER, но это не очень здорово. Дублирование кода и всё такое...

Но на наше счастье у нас в аксиоматике есть слово WordAlias. Которое занимается тем, что определяет синонимы к уже определённым словам.

При этом это именно - синонимы, а не вызов или повторение кода.

 Воспользуемся словом WordAlias:

 WordAlis раза раз
 // - это синоним к слову "раз"

Как только мы это написали, то мы можем писать так:

 5 раз "Нажать {("Стрелка вниз")}"
 2 раза "Нажать {("Стрелка вниз")}" 

Ну и ещё один штрих:

WordAlias "Стрелку вниз" "Стрелка вниз"

Тогда пример можно переписать так:

 5 раз "Нажать {("Стрелку вниз")}"
 2 раза "Нажать {("Стрелку вниз")}" 

Надеюсь, что идея понятна.

Ну и в завершение - хочу сказать, что это был пример использования слова WORDWORKER.

На самом деле при наличии слова WordAlias - изначально можно было написать так:

WordAlias раз LOOP
 // - это синоним к слову LOOP
WordAlis раза раз
 // - это синоним к слову "раз"

Все остальные примеры - остаются без изменений.

Думаю, что в ближайшем будущем - я напишу заметку о том, "как это устроено внутри" со стороны Delphi.

А следующая серия незамедлительно следует вот тут - http://18delphi.blogspot.com/2013/11/gui-2_6.html (в ближайшие 30 мин я её напишу). Но она тоже из разряда "анонсов".

P.S. Что характерно - мы в этой статье практически нигде не упомянули Delphi и код на нём.

Т.е. мы уже "достаточно быстро" оторвались от базового языка и начали оперировать терминами DSL (http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D1%8F%D0%B7%D1%8B%D0%BA_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)

P.P.S. Можно кстати и "по-английски":

WordAlias times LOOP
 // - это синоним к слову LOOP
WordAlias "Press" "Нажать"

Тогда можно написать код:

5 times "Press {('Down')}"

Понятное дело, что грамматика остаётся ФОРМАЛЬНОЙ.

И есть всякие скобочки/закорючки.

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

 Но! Для тестов это по-моему - самое оно.

Тестировщик может открыть код теста и повторить ТО ЖЕ САМОЕ руками.

Конечно он должен знать (или догадываться) о назначении скобочек и закорючек.

Написали тут комментарий про UML и тесты

Поскольку он был публичный - позволю себе его процитировать:

"Ну типа того. Насчет УМЛ для ООП - все ок. А как быть с УМЛ для реляционных баз данных? Вот там тот самый избыточный код действительно даст катастрофическое падения перформанса, ибо оптимизация запросов - сложно формализуемый (неформализуемый) процесс. Ну, например расширения базового документа: добавление тагов, аттрибутов к нему в виде таблиц... Я так вообще использую фиребирд и ВСЯ логика сидит в БД. Это страшный сон. Зато очень эффективно.  Кстати, я вообще интересуюсь твоим опытом, ибо сейчас у меня создана довольно мощная библиотека, почти что фреймворк и практически все новые сущности в ней создаются по шаблонам, меня реально задолбало каждый раз писать одно и то же (почти одно и то же), например для создания нового типа документа на основе базового класса. Намного быстрее было бы это рисовать. Ну а в идеале я уже писал - рисуешь модель - на выходе готовый продукт, остается добавить имплементацию. А если система еще и сама учится... Должно наступить такое время, когда самый тупой манагер диктует роботу, чего он желает и через пару минут получает готовую апликуху, функционал которой только наглядно подтверждает тупость того самого манагера. И для того, чтобы манагер понял, что занимается не своим делом уйдет не пол-года а те самые пары минут"

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

Ну что сказать? Я ТАК И ДЕЛАЮ. Рисую, а не программирую.

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

"Я конечно понимаю, что формализовать, довести до элементарных тест-кейсов каждое действие возможно (это вообще можно встроить в систему) и в результате система сможет тестировать себя сама" - у нас ИМЕННО так и устроено. Тесты встроены в систему. В отладочную версию.

И ещё одно мнение:

"Да, вопросы конечно правильные. Я тоже не раз задавался вопросами относительно правильного тестирования БДшных приложений. Там проблемы даже с тестовыми данными возможны - они сложные и сильно вариативные. И придумать как протестить, например, применение компенсаций и скидок с оборота при обслуживаниям по картам - не такая уж тривиальная задача. Но, думаю, если бы генерилось это дело с модели, всю картину происходящего было бы проще "обозреть"."

Не буду комментировать. Оставлю его открытым.

Прислали ссылку. LibreOffice. Тесты чтения RTF

Прислали хорошую ссылку про монады

Вот - http://www.nuget.org/packages/Monads/

Там кроме всего прочего есть ясное, чёткое и логичное определение монад:

"In functional programming, a monad is a programming structure that represents computations. Monads are a kind of abstract data type constructor that encapsulate program logic instead of data in the domain model. A defined monad allows the programmer to chain actions together to build a pipeline to process data in various steps, in which each action is decorated with additional processing rules provided by the monad. Programs written in functional style can make use of monads to structure procedures that include sequenced operations, or to define some arbitrary control flows (like handling concurrency, continuations, side effects such as input/output, or exceptions)."

Offtopic. Хотел купить "себе лично" AQTime

Хотел купить "себе лично" AQTime.

Но эта  их политика, что "работает на одном компьютере" и надо "разворачивать сервер лицензий" - останавливает...