Разделы портала

Онлайн-тренинги

.
Архитектура автоматизированных функциональных тестов: прагматичный подход к использованию Model-Based техник автоматизированного тестирования
29.09.2008 10:50

Автор: Михаил Давыдов, Luxoft Company (www.luxoft.com)

Суть тестирования — выполнение проверок (в model-based тестировании их принято называть «оракулами»). Очевидно, что при проведении проверок можно и нужно абстрагироваться от пользовательского интерфейса. Проверки выполняются над объектами, представляющими состояние системы (причём, могут использоваться несколько объектов одновременно — например, объекты представляющие текущее и предыдущее состояния системы).

Функциональное автоматизированное тестирование — постановка задачи

Цель функционального тестирования — проверить, что тестируемая система удовлетворяет требованиям документа SRS (функциональной спецификации). Наиболее распространённые техники автоматизированного тестирования — модульное (компонентное, Unit) тестирование и «регрессионное» тестирование (основанное на идее Baseline — сохранении выходных данных тестируемой системы для последующего сравнения) не удовлетворяют этой цели.

Однако существующие на рынке инструменты заточены, как правило, под один из этих двух видов автоматизированного тестирования. Open-source инструменты, как правило, больше подходят для модульного тестирования, а коммерческие инструменты таких компаний как Mercury Interactive, IBM/Rational, Borland/Segue, Empirix и др., скорее подходят для регрессионного тестирования (и значительная часть встроенной в них функциональности рассчитана именно на такой подход).

В строгом смысле слова, единственной техникой функционального тестирования является тестирование на основе формальных спецификаций (Model-based testing). Однако этот подход обычно применяют для промышленных систем, к качеству которых предъявляются повышенные требования. Такие системы, как правило, не имеют развитого графического пользовательского интерфейса. С другой стороны, Model-based тестирование — весьма дорогое удовольствие, требующее как исключительной квалификации сотрудников, так и применения дорогостоящего (даже по сравнению с коммерческими инструментами автоматизации тестирования) и сложного в освоении программного обеспечения.

Итак, возникает вопрос — можно ли использовать привычные инструменты (как коммерческие, так и open-source) для проведения автоматизированного функционального тестирования систем с развитым графическим пользовательским интерфейсом? Может ли такое тестирование быть экономически оправданным (по сравнению с ручным тестированием)? Мой ответ — да. Единственное что для этого надо — понимать цели функционального тестирования, и не увлекаться формой в ущерб содержанию — то есть, использовать «прагматичный» подход. Описываемый здесь подход не уникален (см., например, статью Гарри Робертсона), но в моей статье я постараюсь сосредоточиться не на том, зачем нужно Model-based тестирование, а на архитектуре Model-based тестов, применимой в реальной жизни.

 

Разбираем автоматизированный функциональный тест на составные части

Чтобы разработать оптимальную архитектуру автоматизированных функциональных тестов, надо понять, из каких «частей» состоит типичный функциональный тест.

Итак:

Драйвер интерфейса

Функциональное (как и регрессионное) тестирование — это тестирование методом «черного ящика», то есть, тестирование через непосредственное взаимодействие с пользовательским интерфейсом тестируемой системы. Что для этого нужно — это набор функций, осуществляющих непосредственное взаимодействие с системой, позволяющий, тем не менее, абстрагироваться от конкретной реализации пользовательского интерфейса. Причём, нам придется, как вводить данные в тестируемую систему, так и «захватывать» выходные данные. За это отвечает модуль, называемый «драйвером интерфейса». В качестве драйвера интерфейса можно использовать непосредственно встроенные функции инструмента автоматизации. Препятствием тут может служить их недостаточный уровень абстракции. Ещё одна проблема с использованием встроенных функций — интерфейс функций тестового драйвера должен облегчать интеграцию с другими модулями функционального теста, о которых речь пойдёт дальше.

Хранение внутреннего «состояния» системы

На более высоком, чем пользовательский интерфейс системы уровне абстракции существует такое понятие, как состояние тестируемой системы. Это состояние полностью определяется входными (тестовыми данными). Синоним понятия «состояние системы» — выходные данные системы. Причём, в каждый момент времени существует целый набор параметров системы («переменных состояния») из которых складывается состояние системы. Так, предположим, что мы тестируем текстовый редактор. Состояние главного окна текстового редактора описывается большим набором переменных состояния. Наиболее очевидные из них:

  • наличие открытого документа
  • был ли сохранён редактируемый документ
  • наличие выделения текста
  • наличие текста в буфере обмена
  • координаты курсора

Между переменными состояния существуют сложные взаимосвязи. Так, наличие или отсутствие открытого документа определяет доступность команд «Сохранить» и «Сохранить как...» (это тоже — переменные состояния)

Ещё одна важная особенность состояний тестируемой системы — то, что переменные состояния отдельных частей (компонентов) системы могут не зависеть друг от друга. Точнее говоря, состояния разных компонентов системы имеют ограниченное количество общих переменных состояния. Так, диалог открытия файла того же тестового редактора «знает» только имя и папку последнего открытого файла, но ничего не знает ни о положении курсора, ни о наличии выделения текста в главном окне.

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

На практике это означает, что для каждого компонента («страницы» в случае веб- приложения, либо «окна» в случае Win32-GUI приложения) системы удобно иметь объект (структуру), хранящий все переменные состояния данного компонента. Причём, данные в этом объекте должны храниться в таком виде, который облегчает сравнение состояний разных компонентов, сравнение разных состояний одного и того же компонента, облегчает «сериализацию» (преобразование в другие форматы, в том числе, сохранение в файл и вывод в отчёт о выполнении теста). Итак — ещё один модуль функционального теста — набор классов, представляющих состояние компонентов тестируемой системы, и соответствующие им объекты (далее — объекты состояния компонента системы).

Взаимосвязи между состояниями системы

Здесь придётся сделать небольшое отступление. Дело в том, что взаимосвязи между переменными состояния системы (фактически представляющие формализованную функциональную спецификацию системы) в нашем подходе не выделяются (в отличие от остальных «составляющих» функционального теста) в отдельный компонент. Напротив, при прагматичном подходе к model-based тестированию, эти знания о системе «размазываются» по нескольким компонентам — оракулам, «менеджеру функционального тестирования», генератору тестовых данных (при его наличии), и т.д.

Функциональную спецификацию можно представить в виде набора переменных состояния каждого компонента системы (их, как уже договорились, мы будем хранить в специальных структурах/объектах) с одной стороны, и набора взаимосвязей — как внутренних (между переменными состояния одного компонента) так и внешних — между переменными состояния разными компонентами с другой. Эти взаимосвязи, в идеале, также нужно формализовать и хранить в определённом виде. Очевидно, способ хранения этих взаимосвязей должен учитывать способ хранения самих переменных состояния, и способ использования этих взаимосвязей в тесте. Причём важно понимать, что эти взаимосвязи — это, по существу - функции. Это могут быть настолько простые функции, что их можно хранить просто в «табличном» виде, а могут быть и сложные математические функции. Это тоже накладывает ограничения на способ хранения.

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

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

На практике это означает, что наиболее часто используемые «константы» хранятся в отдельных файлах (не обязательно файлах с данными — это могут быть и программные модули с определениями констант), а более «частные» (и более сложно формализуемые) можно «жестко кодировать» в месте использования. Формат же хранения этих данных должен обеспечивать максимальное удобство, как при редактировании констант, так и при их использовании.

Тестовые оракулы

Суть тестирования — выполнение проверок (в model-based тестировании их принято называть «оракулами»). Очевидно, что при проведении проверок можно и нужно абстрагироваться от пользовательского интерфейса. Проверки выполняются над объектами, представляющими состояние системы (причём, могут использоваться несколько объектов одновременно — например, объекты представляющие текущее и предыдущее состояния системы). Оракулы же хранят и данные о взаимосвязях переменных состояния и частей системы в целом. Поэтому логично выделение оракулов в отдельный компонент (модуль, библиотеку, либо набор классов). В этом же модуле, зачастую, хранятся и взаимосвязи между переменными состояния — как в виде общих констант, так и жестко закодированные в «теле» проверок.

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

  Лирическое отступление. Понятно, что работа с переменными состояния весьма трудоёмка, и если разрабатывать функциональные тесты по описываемой архитектуре — до получения первых результатов может пройти несколько месяцев (и то, при условии того, что у Вас за плечами уже есть успешно выполненные по этой методологии проекты). Итак, как можно использовать «недоделанные» функциональные тесты — с минимумом функциональных оракулов и при работе с минимальным количеством переменных состояния? Ответ прост — регрессионные тесты. «Чекпойнты» (они же — Verification Points ) — тоже подтип оракулов. Причём, подтип, требующий минимальных усилий при разработке. Так что, начать можно с использования регрессионных оракулов. Единственная тонкость — используемые Вами чекпойнты должны быть просты, надёжны, и не требовать записи (то есть, baseline должен генерироваться автоматически при первом выполнении теста). Ещё один способ избежать программирования сложных оракулов на первых порах — использовать «ручные» оракулы — то есть, ограничиться снятием снэпшотов для последующего ручного анализа. Пока тестов не много, это работает (впрочем, как и регрессионное тестирование).

Менеджер (диспетчер) функционального тестирования

Располагая знаниями о взаимосвязях между компонентами системы и переменными состояния внутри каждого компонента, мы можем с помощью оракулов предсказать, в какое состояние переведёт систему то или иное действие. Однако, оракулы сами по себе ничего не «знают» о том, как выполнять действия над системой (это «знает» драйвер интерфейса), а драйвер интерфейса «не в курсе», какие проверки необходимо вызывать при выполнении того или иного действия. Кроме того, для работы оракулов нужны значения переменных состояния (причём, как правило, используются сразу несколько «снимков» состояния системы — например, «снимок» текущего и предыдущего состояний). «Захват» и хранение состояний компонентов системы также должны координироваться отдельно.

Таким образом, нам понадобиться ещё один модуль функционально теста «менеджер (диспетчер) функционального тестирования». Как у всякого менеджера, его основная задача — координировать работу подчинённых — в данном случае, драйвера пользовательского интерфейса и оракулов, и предоставлять им необходимые в их работе данные.

Тестовые данные (тест-кейс)

Для выполнения теста нам не хватает «самой малости» — последовательности действий (набора тестовых данных). Существует несколько подходов к созданию и хранению тестовых данных. Можно тестовые данные задавать вручную. В этом случае проще всего в «главном» модуле тестов непосредственно вызывать методы (это может быть и единственный метод) менеджера функционального тестирования, которые и будут инициировать как непосредственное взаимодействие с тестируемой системой, так и проверку результатов этих действий. Очевидно, что «параметры» этих вызовов, при желании можно хранить в отдельном файле, который и будет соответствовать понятию «тест кейса» в ручном тестировании.

Если система сложная, для обеспечения полноты набора тестов нам могут понадобиться десятки, сотни, и тысячи тест кейсов. В этом случае нам будет нужен ещё один модуль.

Автоматизированная генерация тест кейсов

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

Если говорить об автоматизированной генерации тест-кейсов, то тут (в рамках «прагматичного» подхода) имеет смысл использовать один из двух различных методов ограничения набора тестовых данных:

1. Ограничиться генерацией входных данных для отдельных компонентов системы и их непосредственного взаимодействия с соседями. То есть, для каждого компонента, набор действий и тестовых данных генерируется отдельно. Выбор комбинаций тестовых данных можно поручить специализированным компонентам (например, программе ALLPAIRS). Это — «классический» подход к автоматизированной генерации данных. Его недостаток — необходимости в отдельном генераторе данных для каждого компонента (экрана, окна, страницы) системы.

2. Создать «модель состояний» системы, описывающую доступные действия над системой в каждом её состоянии. Набор данных для каждого действия можно ограничить по «доменному» принципу (на каждую ветку выполнения по одному набору значений). Затем случайным образом сгенерировать последовательности действий с соответствующими входными данными. Для этого придется ввести систему «приоритетов» различных действий, систему ограничений длинны последовательности действий, и т.п. Такой способ можно назвать «Smart Monkey Testing», он упоминается Кемом Канером в его презентации «High Volume Automated Testing». Недостаток такого подхода — то, что при всей широте тестового покрытия, его «глубина» при тестировании достаточно сложной системы и разумном (до нескольких дней) времени выполнении такого теста весьма небольшая. И, понятно, этот метод не даёт никаких гарантий (хотя и позволяет находить дефекты, которые никогда бы не были найдены при использовании других подходов). Ещё одна проблема — отладка такого теста.

Архитектура функциональных тестов

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

Как видно, функциональный тест состоит из нескольких «программных» модулей (обозначены прямоугольниками со сглаженными углами), и нескольких «модулей данных» (на практике, модули данных также могут быть программными, то есть содержать код — например, объявление констант), которые обозначены в виде листа бумаги с загнутым уголком:

Тест-кейс содержит последовательность действий над тестируемой системой и тестовые данные для каждого действия (может состоять из нескольких файлов — например, тестовые данные можно выделить в отдельный файл, а «драйвер данных» этого файла (который и будет вызвать методы менеджера функционального тестирования) — в другой).

Менеджер функционального тестирования вызывается из тест-кейса для выполнения действий над тестируемой системой и выполнения проверок. Он управляет сохранением состояний системы, вызовом функций «интерфейсного драйвера» и соответствующих им оракулов.

Менеджер функционального тестирования удобнее всего выделить в отдельный модуль.

Библиотека оракулов. Содержит проверки функциональности. Использует константы, описывающие поведение системы (могут «физически» в том же файле, что и сами оракулы). Использует объекты состояния системы для получения переменных состояния каждого компонента системы. Объекты состояния системы для оракулов предоставляет «менеджер функционального тестирования».

Библиотека классов состояния системы. Содержит классы, хранящие переменные состояния (выходные данные) для каждого компонента (экрана, окна, страницы).

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

Константы, описывающие поведение системы — взаимосвязи между переменными состояния (а также взаимосвязи между частями системы) могут быть выделены в отдельный файл «данных», а могут быть составной частью библиотеки оракулов

Генератор тестов — опциональный компонент, который может использоваться для генерации тестовых данных и последовательности действий над тестируемой системой. В зависимости от используемого подхода, генератор тестов может либо генерировать данные, используемые затем в качестве тест-кейса, а может и в реальном времени вызывать менеджер функционального тестирования.

Отчёт о выполнении теста содержит сообщения всех программных модулей — менеджера функционального тестирования (о выполненных действиях над системой), библиотеки оракулов (о проваленных проверках, а также о результатах проверок, требующих дополнительного «ручного» анализа), интерфейсного драйвера (о проблемах взаимодействия с пользовательским интерфейсом системы).

Рис.1. Архитектура типичного автоматизированного функционального теста

 

Разработка функциональных тестов с использованием коммерческих инструментов автоматизации тестирования, или «дьявол в деталях»

Пока что, мы почти не говорили о том, как использовать возможности существующих инструментов автоматизации тестирования для реализации нашей архитектуры. Дьявол, как известно, кроется в деталях, и с того момента, как я задумался о применении Model-Based подхода, до того момента, как стало ясно, как это можно сделать, используя стандартные инструменты (в моём случае — Mercury Quick Test Pro), прошло больше года (впрочем, это время не прошло впустую). Увы, те статьи, которые я видел по model-based тестированию, прекрасно расписывают все его «вкусности», но мало говорят о том, как его применять на практике. Об этом и поговорим.

Общие библиотеки функций

Первое, что бросается в глаза — Model-Based тестирование основано на манипуляции данными тестируемой системы — данными, имеющими сложную структуру. Фактически, нам для проверки правильности работы алгоритмов тестируемой системы придётся писать собственные, пусть, упрощенные, версии тех же алгоритмов (метод функциональной эквивалентности). Увы, ни один существующий коммерческий инструмент (кроме, разве что, Rational Functional Tester'a, который унаследовал это прекрасное свойство от своих языков тестов — VB.Net и Java) не имеет развитых средств хранения и манипулирования данными. Это означает только одно — нам придется разрабатывать эти средства самостоятельно.

Наиболее распространенные типы данных, с которыми нам придется работать — табличные данные, двумерные и одномерные массивы, а также «ассоциативные массивы». Кроме структур, умеющих хранить эти типы данных (они, зачастую, уже есть в скриптовом языке) понадобятся функции для манипуляции данными. Надо научиться сравнивать данные, искать их и переносить по частям из одного «объекта» в другой. В случае QTP, мне пришлось написать библиотеки для работы с массивами, создать класс для работы с табличными данными, библиотеку для работы с объектами типа "Dictionary" (ассоциативными массивами) и много других функций для манипуляции данными.

Для того чтобы работать с данными системы, их сначала надо получить, причём в нужном нам виде — виде «объектов», с которым мы и будем работать. Для этого понадобятся дополнительные функции «съема» данных. Главное, что их будет отличать от встроенных функций инструмента автоматизации — данные нам надо будет снимать «оптом», а не «в розницу». Вводить данные в систему также удобнее всего «оптом» — тут тоже понадобятся специальные функции (заточенные под конкретную тестируемую систему). Например, если речь идёт о вёб-приложении, нам понадобятся функции для получения значений элементов «вёб-форм» (то есть, всех элементов INPUT), и функции для заполнения тех же форм. Содержимое формы удобно представить в виде ассоциативного массива «имя элемента — значение». По существу, полученный массив будет готовым поднабором переменных состояния страницы. Заполнять форму удобно в том же формате — передавать строку вида "элемент1=значение1;элемент2=значение2;..." (это — самый удобный способ задать ассоциативный массив).

Единственным «вещественным» результатом выполнения теста является отчёт о выполнении. Поэтому очень важно уметь правильно работать с отчётом — например, разбивать отчёт на отдельные «шаги», отслеживая результат каждого шага, уметь записывать в отчёт наборы данных в удобном формате (например, в виде таблиц HTML). Для этого тоже понадобится писать свои функции.

Константы, описывающие поведение системы, тестовые данные надо где-то хранить. Хранить в удобном формате, чтобы их можно было легко редактировать, а ещё легче — использовать в тесте, избегая путаницы. Для этого понадобится ещё один набор функций. К хранению данных может быть много разных подходов. Можно хранить данные в таблицах Excel либо comma-delimited (CSV) файлах, можно — в формате XML, можно — в самом тесте в виде строк (в уже упоминавшемся формате "элемент=значение;..." — мне этот подход оказался ближе остальных). Увы, и тут встроенной функциональностью инструмента обойтись не удастся (с ужасом вспоминаю результаты своих попыток использовать для этих целей DataTable QuickT est P ro ).

Классы состояния системы

Очень удобно, если «компоненты» системы имеют много общих элементов интерфейса (и, соответственно, общих переменных состояния). В этом случае для хранения состояний разных «экранов» можно использовать одни и те же структуры данных. В общем же случае, на каждый «экран» надо иметь свою, отдельную структуру данных, с другой стороны, некоторые элементы этой структуры, тем не менее, могут быть и общими.

Очевидно, что реализация классов очень сильно зависит от скриптового языка.

Прекрасно, если язык объектно-ориентированный. В этом случае можно будет наследовать классы состояния друг от друга. Мне достался несколько менее удачный случай — скриптовый язык QTP — VBScript является объектным, но не объектно-ориентированным. Соответственно, классы друг от друга наследовать нельзя. Но зато, классы могут иметь свои методы, и, как и любой язык с поздним связыванием, VBScript не проводит проверку соответствия типов. Поэтому, классы могут иметь «одноименные» методы — весьма удобно (хоть и не «красивый стиль» — придется активно применять «национальный китайский способ повторного использования кода» — Copy-Paste ).

Ещё несколько худший вариант — язык, поддерживающий структуры, но не поддерживающий классы. Тут придется ограничиться хранением данных в структурах. Тоже неплохо.

Ещё хуже — если язык поддерживает только скалярные типы данных и массивы (WinRunner/TCL, Rational Robot/SQABasic и прочие инструменты первого поколения, кроме, разве что SilkTest 'a, сильно обогнавшего время). Единственный вариант, который я вижу в этом случае — писать объекты на «полноценном» ООП-языке,например, C++ или Delphi, и работать с ними через DLL . Остальные варианты напоминают, нет, не «забивание гвоздей микроскопом», а скорее попытки подковать блоху паровым молотом с ЧПУ ;). Собственно, идеи о внедрении Model-Based практик впервые пришли мне в голову ещё в период моей работы с WinRunner, но тогда всё упёрлось именно в отсутствие способов реализации объектов состояния на TCL — все решения, которые я видел, были слишком громоздкими.

Итак, вернёмся к примеру с текстовым редактором, и попробуем представить, как будет выглядеть (в упрощенном виде) класс состояния главного окна.

<Класс>

<Cвойство>Открытый файл </свойство>
<Cвойство>Текст </свойство>
<Cвойство>Выделение текста </свойство>
<Свойство тип:ассоциативный массив>Текущие парметры

<элемент>Текущая колонка</элемент>
<элемент>Текущая строка </элемент>
<элемент>Колонка начала выделения</элемент>
<элемент>Строка начала выделения</элемент>
<элемент>Колонка конца выделения</элемент>
<элемент>Строка конца выделения</элемент>

</Cвойство>
<Свойство тип:ассоциативный массив>Текущий Формат

<элемент>Шрифт</элемент>
<элемент>Кегль</элемент>
<элемент>Курсив</элемент>
<элемент>Жирный</элемент>
<элемент> П одчеркивание</элемент>

</Cвойство>
...

</Класс>

Отдельно понадобятся классы состояния для главного меню, для контекстного меню, для диалогов (например, диалог выбора формата). Будут нужны и функции, «схватывающие состояния» каждого из компонентов — в зависимости от скриптового языка, они могут быть либо методами объекта состояния, либо отдельными функциями.

Оракулы

Наиболее часто используемый тип проверок в функциях-оракулах — сравнение данных. Приведу несколько примеров проверок для того же текстового редактора:

Процедура приОткрытииДиалогаФормата()

Ассоциативный массив Разница;

Разница:= Сравнить АссоциативныеМассивы (ГлавноеОкно.Teкущий формат, ОкноФормата.Формат) ;

Если (ГлавноеОкно.ВыделениеТекста= "") то

Если (Разница.КоличествоЭлеметов>0 ) то

СообщитьОшибкуВЛог («приОткрытииДиалогаФормата», МассивВТекст(Разница.Элементы));

Конец условия

Иначе

/Другие проверки/

Конец условия;

Конец Процедуры;

 

Процедура приОткрытииКонтекстногоМеню()

Если (ГлавноеМеню.МожноВставить<>КонтекстноеМеню.МожноВставить) то

СообщитьОшибкуВЛог ("приОткрытииКонтекстногоМеню","Не совпала информация о наличии текста в буффере обмена с главным меню");

Конец условия;

/Другие проверки/

Конец Процедуры;

Я не буду здесь приводить примеры констант, описывающих поведение системы в контексте выбранного примера, так как использование таких констант здесь выглядит несколько натянутым. Приведу более близкий моей практике пример — проверка правильности сортировки таблицы (после выполнения сортировки по одной из колонок). Для такой проверки нам надо будет знать формат каждой колонки (число/строка/дата). Если таблиц много, то здесь удобно применить константы следующего вида (пример на VBScript):

Set sorting_consts = CreateObject("Scripting.Dictionary")

Sorting_consts.Add "Products","Product Id=number;Product description=string;Expiry date=date"

Sorting_consts.Add "Orders","Order Id=number;Product Id=number;Order Date=date;Manager=string"

public function dictFromParamString(aString)

Dim PairsArr,aDict,logicalName,value,i

set aDict = CreateObject("Scripting.Dictionary")

PairsArr = Split(aString,";")

for i = 0 to UBound(PairsArr)

logicalName = Split(PairsArr(i),"=")(0)

value = Split(PairsArr(i),"=")(1)

if logicalName <>"" then

If not aDict.Exists(logicalName) Then

aDict.add logicalName,value

else

aDict(logicalName)=value

end if

end if

next

set dictFromParamString = aDict

end function

 

function getColumnSortingType(Screen,column)

dim d

set d = dictFromParamString(Sorting_consts(Screen))

getColumnSortingType = d(column)

end function

Потом эти же константы нам могут пригодиться и для других проверок. Например, можно будет проверить формат данных в колонках таблицы, используя эти же константы.

Другой пример — взаимосвязь переменных состояния на одном экране. Пусть это форма поиска данных с таблицей результатов. Тогда у нас будут такие переменные состояния:

SearchForm.Properties — ассоциативный массив (Dictionary):

SearchForm . Properties («таблица_пуста») — показываются ли результаты поиска

SearchForm . Properties («экспорт_результатов_разрешен») — можно ли нажать кнопку «Экспорт» (если данных нет — кнопка заблокирована)

SearchForm . Properties («первый_раз») — если мы первый раз на этой странице и не успели ничего изменить, значения фильтров выставлены по умолчанию

SearchForm.Filters — ассоциативный массив (Dictionary):

SearchForm . ResultsTable — объект типа ResultsTable (пользовательский класс для работы с таблицами)

Для проверки зависимостей между переменными состояния нам понадобится константа SearchForm_dependancies:

Set SearchForm_dependancies = CreateObject("Scripting.Dictionary")

SearchForm_dependancies.Add "таблица_пуста=true","экспорт_результатов_разрешен=fase"

SearchForm _ dependancies . Add «первый_раз= true »," экспорт_результатов_разрешен= fase ;таблица_результатов_пуста= true "

Чтобы проверить значения фильтров по умолчанию, используем другую константу:

Set SearchForm_DefaultFilterValues = dictFromParamString("Order Id=;Manager=admin;Date="&CStr(Date(Now()))

Соответствующие функции-оракулы, использующие данные константы довольно просты, но требуют использования некоторых функций, отсутствующих в VBScript — для сравнения объектов Dictionary, прежде всего.

Очевидно, все те же задачи можно было решить, используя XML либо файлы CSV. Только вот таблицы CSV — двумерны (а некоторые коллекции из примера — трёхмерны), а использовать XML в данном случае неоправданно.

Менеджер функционального тестирования

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

Хранение и «запоминание» состояния компонентов

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

Функции «действий»

Для каждого действия над тестируемой системой создается «функция-обертка», выполняющая несколько действий:

  1. Вызов соответствующей(их) функции(й) интерфейсного драйвера
  2. В случае неудачи — обработка исключительной ситуации и выход из функции
  3. Захват (в случае необходимости) новых текущих состояний всех изменивших своё состояние компонентов (в практике веб тестирования, своё состояние обычно меняет единственный компонент — текущая страница).
  4. Вызов (в случае необходимости) соответствующей функции-оракула

Обработка исключительных ситуаций

В самом простом случае, в исключительной ситуации выставляется флаг ошибки, в результате чего последующие вызовы функций-обёрток не проходят до тех пор, пока не будет вызвана функция, инициализирующая состояние системы заново (в нашем примере — перезапуск редактора)

Тест-кейс

В простейшем случае, тест-кейс состоит из вызовов «функций-оберток» менеджера функционального тестирования. Можно организовать и обработку исключительных ситуаций. Посмотрим, как будет выглядеть простейший тест-кейс:

Тест-кейс ( VBScript ):

Ftm _ LaunchEditor ‘ Запустить тестируемое приложение - редактор

Ftm _ MainMenu _ CmdOpen ' Выбрать элемент меню File>Open...

Ftm_OpenDlg_OpenFile "C:\temp\test.rtf" ' В диалоге открытия файла ввести имя файла и нажать 'Open'

Ftm _ MainWindow _ SetCarret 10,10 ' Установить позицию курсора в окне редактирования — 10, 10

Ftm _ MainWindow _ InvokePopUp ' Вызвать контекстное меню

Ftm _ PopUp _ Close 'Надать Esc и проверить что меню исчезло

Ftm_MainWindow_SelectText 1,1,10,10 ' Выделить текст

Ftm _ MainWindow _ SetCarret 5,5 ' Установить позицию курсора в окне редактирования — 5, 5

Ftm _ MainWindow _ InvokePopUp ' Вызвать контекстное меню

Ftm _ PopUp _ Copy ' Выбрать элемент контекстного меню Copy

Ftm _ MainWindow _ InvokePopUp ' Вызвать контекстное меню

Ftm_CloseEditor() 'Закрыть тестируемое приложение

А теперь проследим за вызовами функций (в квадратных скобках указано имя модуля) (проверка исключительных ситуаций опущена, встроенные функции и функции общих библиотек опущены):

Ftm_LaunchEditor() [ftmanager.vbs]

Uid_LaunchEditor() [uidriver.vbs]

set stateMainWindowCurrent=CMainWindow.Capture() [states.vbs]

set stateMainMenuCurrent=CMainMenu.Capture() [states.vbs]

Oracle_OnLaunchEditorChecks() [oracles.vbs]

Oracle_CommonMainWindowCheck()[oracles.vbs]

Oracle_CommonMainMenuCheck()[oracles.vbs]

Oracle_NoFileMainWindowCheck()[oracles.vbs]

Oracle_NoFileMainMenuCheck()[oracles.vbs]

Ftm_MainMenu_CmdOpen () [ftmanager.vbs]

Uid_MainMenu_CmdOpen()[uidriver.vbs]

Set stateOpenDlgCurrent=COpenDialog.Capture() [states.vbs]

Oracle_ OnOpenDlgOpenChecks()[oracles.vbs]

Ftm_OpenDlg_OpenFile [ftmanager.vbs]

Uid_OpenDlg_OpenFile "C:\temp\test.rtf" [uidriver.vbs]

Set stateMainWindowPrevious=stateMainWindowCurrent [states.vbs]

Set stateMainWindowCurrent=CMainWindow.Capture()[states.vbs]

Set stateMainMenuPrevious=stateMainMenuCurrent [states.vbs]

Set stateMainMenuCurrent=CMainMenu.Capture()[states.vbs]

Oracle_OnFileOpened()[oracles.vbs]

Oracle_CommonMainWindowCheck()[oracles.vbs]

Oracle_CommonMainMenuCheck() [oracles.vbs]

Oracle_FileLoadedMainWindowCheck() [oracles.vbs]

Oracle_FileLoadedMainMenuCheck()[oracles.vbs]

Ftm_MainWindow_SetCarret 10,10 [ftmanager.vbs]

Uid_MainWindow_SetCarret 10,10 [uidriver.vbs]

stateMainWindowCurrent.CarretPos.Col=10 [states.vbs]

stateMainWindowCurrent . CarretPos . Line =10 [states.vbs]

Ftm_MainWindow_InvokePopUp [ftmanager.vbs]

Uid_MainWindow_InvokePopUp() [uidriver.vbs]

Set statePopUpCurrent=CPopUp.Capture [states.vbs]

Oracle_OnPopUpInvoked()[oracles.vbs]

Ftm_PopUpClose [ftmanager.vbs]

Uid_PopUpClosed()[uidriver.vbs]

...

Заключение

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