вторник, 19 ноября 2013 г.

Ещё взгляд на тестирование

У меня есть чужой материал, который для меня лично - ценен.

Хочу его опубликовать.

Итак.

Трофимов Андрей пишет:

В статье http://18delphi.blogspot.ru/2013/04/blog-post_30.html упоминается использование макросов.

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

Можно даже пойти дальше, и сравнить несколько примеров скриптов, проверяющих работу редактора.


С одной стороны, например, возьмем редактор MS Word (язык VBA) и напишем автоматизированный тест на языке FORTH (для другого текстового редактора).

Подразумевается, что после выполнения скрипта текст редактора сохраняется в файл с Script_name.out и сравнивается с уже заранее приготовленным Script_name.etalon. 

Пример 1.

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

Цель: 
Ввести 3 параграфа с уникальным текстом жирным, подчеркнутым стилем и курсивом.

Решение задачи для MS Word:

Sub Script_check_font()
    Selection.Font.Bold = wdToggle
    Selection.TypeText Text:="Текст, введенный жирным шрифтом."
    Selection.Font.Bold = wdToggle
    Selection.TypeParagraph
    Selection.Font.Italic = wdToggle
    Selection.TypeText Text:="Текст, введенный курсивом."
    Selection.Font.Italic = wdToggle
    Selection.TypeParagraph
    Selection.Font.UnderlineColor = wdColorAutomatic
    Selection.Font.Underline = wdUnderlineSingle
    Selection.TypeText Text:="Подчеркнутый текст."
    Selection.Font.UnderlineColor = wdColorAutomatic
    Selection.Font.Underline = wdUnderlineNone
End Sub

Все достоинства, думаю, можно не перечислять.

Но стоит отметить главные: «плоская» структура кода (без операторов IF, WHILE и т.д.); понимание работы кода для любого человека, владеющим английским языком.

Решение задачи для другого редактора: 

Небольшое пояснение. Работа с редакторами немного различается. Например, выбранный стиль данного редактора распространяется только на один абзац текста (стиль не нужно отключать после создания нового абзаца).

: "Тест проверки шрифтов"
    оп::Шрифт_Жирный
    'Текст, введенный жирным шрифтом.' emitstring
    'Enter' key
    оп::Шрифт_Курсив
    'Текст, введенный курсивом.' emitstring
    'Enter' key
    оп::Шрифт_Подчеркнутый
    'Подчеркнутый текст.' emitstring
; // "Тест проверки шрифтов"

На данном этапе текст скриптов почти идентичен.

Теперь можно переписать скрипт для FORTH.

Переводим и описываем в словаре слова:

//оп::Шрифт_Жирный
    WordAlias "Выбрать жирный шрифт" оп::Шрифт_Жирный

//оп::Шрифт_Курсив
    WordAlias "Выбрать курсив" оп::Шрифт_Курсив

//оп::Шрифт_Подчеркнутый
    WordAlias "Выбрать подчеркнутый шрифт" оп::Шрифт_Подчеркнутый

//emitstring
    : "Ввести строку" STRING IN aString
        aString emitstring
    ;

//key
    : "Нажать" STRING IN aString
        aString key
    ;

: "Тест проверки шрифтов"
    "Выбрать жирный шрифт"
    "Ввести строку {('Текст, введенный жирным шрифтом.')}"
    "Нажать {('Enter')}"
    "Выбрать курсив"
    "Ввести строку {('Текст, введенный курсивом.')}"
    "Нажать {('Enter')}"
    "Выбрать подчеркнутый шрифт"
    "Ввести строку {('Подчеркнутый текст.')}"
; // "Тест проверки шрифтов"

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

Упростим еще:

:  "Новый абзац"
    "Нажать {('Enter')}"
; //  "Новый абзац"

:  "Ввести строку 'Текст, введенный жирным шрифтом.'"
    "Ввести строку {('Текст, введенный жирным шрифтом.')}"
; //  "Ввести строку 'Текст, введенный жирным шрифтом.'"

:  "Ввести строку 'Текст, введенный курсивом.'"
    "Ввести строку {('Текст, введенный курсивом.')}"
; //  "Ввести строку 'Текст, введенный курсивом.'"

:  "Ввести строку 'Подчеркнутый текст.'"
    "Ввести строку {('Подчеркнутый текст.')}"
; //  "Ввести строку 'Подчеркнутый текст.'"

: "Тест проверки шрифтов"
    "Выбрать жирный шрифт"
    "Ввести строку 'Текст, введенный жирным шрифтом.'"
    "Новый абзац"
    "Выбрать курсив"
    "Ввести строку 'Текст, введенный курсивом.'"
    "Новый абзац"
    "Выбрать подчеркнутый шрифт"
    "Ввести строку 'Подчеркнутый текст.'"
; // "Тест проверки шрифтов"

Думаю, стало еще понятнее.

Можно упростить еще:

: "Ввести три строки текста: первая - жирным, вторая - курсивом и третья - подчеркнутым шрифтом"
    "Выбрать жирный шрифт"
    "Ввести строку 'Текст, введенный жирным шрифтом.'"
    "Новый абзац"
    "Выбрать курсив"
    "Ввести строку 'Текст, введенный курсивом.'"
    "Новый абзац"
    "Выбрать подчеркнутый шрифт"
    "Ввести строку 'Подчеркнутый текст.'"
; //

: "Тест проверки шрифтов"
    "Ввести три строки текста: первая - жирным, вторая - курсивом и третья - подчеркнутым шрифтом"
; // "Тест проверки шрифтов"

Примечания: из теста убраны все проверки: что курсор находится в редакторе, что переключение шрифта включает (и не отключает) нужное значение.

Такой код позволит любому человеку воспроизвести сценарий скрипта руками, не разбираясь в программировании.

Весь помещенный код в словарь можно вызывать любое количество раз и не описывать его вновь.

Комбинируя различные слова можно воспроизводить сложные сценарии, даже трудно воспроизводимые вручную (например, кликнуть по четко определенной координате контрола или провести курсором мыши с определенной скоростью по заданным координатам).

Достоинства кода очевидны: понять и выполнить алгоритм работы теста способен любой человек.

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

Пример 2.

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

Цель:
Ввести 6 параграфов с уникальным текстом. Выделить параграфы 3 и 4. Удалить их.

Решение задачи для MS Word:

Sub Script_check_delete_3_and_4_paragraph()
'
' Script_check_delete_3_and_4_paragraph Макрос
'
'
    Selection.TypeText Text:="Вводим 1 параграф."
    Selection.TypeParagraph
    Selection.TypeText Text:="Вводим 2 параграф."
    Selection.TypeParagraph
    Selection.TypeText Text:="Вводим 3 параграф."
    Selection.TypeParagraph
    Selection.TypeText Text:="Вводим 4 параграф."
    Selection.TypeParagraph
    Selection.TypeText Text:="Вводим 5 параграф."
    Selection.TypeParagraph
    Selection.TypeText Text:="Вводим 6 параграф."
    Selection.MoveUp Unit:=wdLine, Count:=5
    Selection.MoveLeft Unit:=wdCharacter, Count:=18
    Selection.MoveDown Unit:=wdLine, Count:=2
    Selection.MoveDown Unit:=wdLine, Count:=2, Extend:=wdExtend
    Selection.Delete Unit:=wdCharacter, Count:=1
End Sub

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

Решение задачи для другого редактора:

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

Также дополним словарик.

Получится так:

WORDWORKER раза INTEGER IN aCount
     aCount LOOP ( WordToWork DO ) 
;

//pop:editor:ParaDown
    : "Перейти на параграф вниз в текущем редакторе"
         focused:control:push pop:editor:ParaDown
    ; 


: "Выделить параграфов" INTEGER IN aCount
    aCount 1 - >>> aCount
    OBJECT VAR l_Editor
    focused:control:push >>> l_Editor 
    l_Editor pop:editor:ParaHome
    // - чтобы выделять с начала параграфа, иначе эталоны будут нестабильны
    cc:StartSelBlock
    "{(aCount)} раз {(@ "Перейти на параграф вниз")}"
    l_Editor pop:editor:ParaEnd
    // - чтобы выделять ДО конца параграфа, иначе эталоны будут нестабильны
    cc:EndSelBlock
;

: "Тест проверки удаления 3 и 4 параграфа"
    "Ввести строку {('Вводим параграф 1.')}"
    "Новый абзац"
    "Ввести строку {('Вводим параграф 2.')}"
    "Новый абзац"
    "Ввести строку {('Вводим параграф 3.')}"
    "Новый абзац"
    "Ввести строку {('Вводим параграф 4.')}"
    "Новый абзац"
    "Ввести строку {('Вводим параграф 5.')}"
    "Новый абзац"
    "Ввести строку {('Вводим параграф 6.')}"
    3 раза ( focused:control:push pop:editor:ParaUp )
    "Выделить {(2)} параграфов"
    cc:Del
; // "Тест проверки удаления 3 и 4 параграфа"

Теперь упростим и переведём код на русский язык:

: "Ввести новый параграф раз"  INTEGER IN aCount
    INTEGER VAR aContext
    STRING VAR aContext_str
    1 >>> aContext
    aCount раз ( 
        [[ 'Вводим параграф ' aContext IntToStr '.' ]] strings:Cat >>> aContext_str
        "Ввести строку {(aContext_str)}"
        aContext 1 + >>> aContext 
        aContext БОЛЬШЕ aCount IF
            EXIT
        ENDIF
        "Новый абзац"
  )
; // "Ввести новый параграф раз"

//focused:control:push
    WordAlias "Контрол в фокусе" focused:control:push

//pop:editor:ParaUp
    : "Перейти на параграф вверх в текущем редакторе"
        "Контрол в фокусе" pop:editor:ParaUp
    ; 

//cc:Del
     WordAlias "Удалить" cc:Del

: "Тест проверки удаления 3 и 4 параграфа"
    "Ввести новый параграф {(6)} раз"
    3 раза ( "Перейти на параграф вверх в текущем редакторе" )
    "Выделить {(2)} параграфов"
    "Удалить"
; // "Тест проверки удаления 3 и 4 параграфа"

Последние штрихи:

: "Ввести 6 уникальных параграфов, выделить третий и четвертый, и удалить их"
    "Ввести новый параграф {(6)} раз"
    3 раза ( "Перейти на параграф вверх в текущем редакторе" )
    "Выделить {(2)} параграфов"
    "Удалить"
; // "Ввести 6 уникальных параграфов, выделить третий и четвертый, и удалить их"

: "Тест проверки удаления 3 и 4 параграфа"
     "Ввести 6 уникальных параграфов, выделить третий и четвертый, и удалить их"
; // "Тест проверки удаления 3 и 4 параграфа"

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

Но в коде словаря появился оператор IF. Это абсолютно не страшно, т.к. для тестировщика это условие останется невидимым (в словаре).

Заключение:

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

Но если ставить целью – разделение обязанностей программистов и тестировщиков, то применения более простого и интуитивно-понятного кода скрипта поможет этого добиться.

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

Программистам – решение поставленных задач (исправление найденных ошибок, реализации нового функционала, рефакторинг кода и др.).

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

----------------

 Не буду комментировать эту статью, хотя у меня и есть свой собственный взгляд, отличный от взгляда автора.

Но - повторю - мне материал - сам по себе ценен.

----------------

P.S. Offtopic. "Редакционная политика - изменилась".

Поскольку я не раз уже слышал примерно следующее - "тебя слишком много, а мыслей слишком мало", то я решил ограничить количество публикаций в блоге - ОДНОЙ в неделю.

Но зато - ХОРОШЕЙ (надеюсь) и объёмной. И проработанной.

Посему - публикую этот материал и беру тайм-аут до weekend'а.

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

По приведённому ранее плану - http://18delphi.blogspot.ru/2013/11/gui_6.html

В плане последнего решения "редакции" - видимо сразу несколько статей в одну объединю.

4 комментария:

  1. Спасибо за интересную статью.
    С подкатами на главной странице стало намного лучше,
    теперь не приходится долго прокручивать страницу.

    ОтветитьУдалить
    Ответы
    1. "С подкатами на главной странице стало намного лучше"
      -- ну я лично - не большой сторонник "лишних ссылок", но если вам нравится, то - ПОЖАЛУЙСТА :-)

      Удалить
    2. "Спасибо за интересную статью."
      -- раз понравилось - ставьте "лайк" :-) не стесняетесь "быть порядочными"...

      Я вот - всегда ставлю.. Понравившимся статьям...

      Удалить
    3. мне лично - "по-барабану" лайки.. но ведь "издатели" говорят - "лайков нет - статья массам неинтересна"..

      Удалить