Что такое?
Выполнение действий над элементами графического интерфейса в случайном порядке.
Для чего нужно?
Человек, выполняющий тестирование, это Homo sapiens, т.е. он обладает неким интеллектом. Этот самый интеллект, мешает (очень редко, но мешает) ему находить «нелепости поведения» приложения связанные с непредвиденными ситуациями. Он просто не может представить себе настолько нелогичную ситуацию.
Пользователь же, намного превосходит QA в количестве и может значительно уступать ему в IQ. Отсюда, вероятность непредвиденного поведения пользователя отнюдь не крайне мала.
Итак, что нам, обладая свободными ресурсами и желанием, мешает принять меры по предотвращению подобных ситуаций? — Ничего.
Теперь сформулируем конкретные задачи, в которых «бессмысленное клацанье» по кнопкам может быть полезно:
- Дополнить существующее тестирование стабильности приложения путем введения модели нелинейного поведения пользователя в GUI.
- Исследовать потребление ресурсов при всех возможных вариантах работы приложения (инициированные из GUI).
Во-первых, вариант тестирования, описанный в данной статье, должен быть использован, действительно, только как дополнение к существующему тестированию графического интерфейса. Полагаться лишь на хаотичное «клацанье» по кнопкам – по меньшей мере, глупо. Нет никакой проверки, что именно происходит да и происходит ли вообще. Поэтому первый вариант можно рассматривать как дополнительное негативное тестирование на стабильность.
Второй же, максимально эффективен на бесконечном отрезке времени, что часто, невозможно. Поэтому, выбирая период измерений, следует исходить из сложности приложения, его типа и назначения. Например, наверно нет смысла 24 часа гонять «несерверное» приложение, состоящее из двух кнопок и одного чекбокса, которое умножает что-то на два, а потом результат делит пополам.
Как делать будем?
Дальнейшее описание предназначено для тестирования приложений на платформе Windows.
Предлагаю воспользоваться связкой python + pywinauto. Хотя pywinauto и имеет некоторые ограничения в плане доступа к элементам окна, для большинства случаев этого должно быть достаточно.
Честно говоря, альтернативы я не вижу. Все знакомые мне средства автоматизации тестирования GUI не обладают динамичность, показанной ниже – уже во время выполнения теста получать список контролов, определять их тип и выполнять допустимое действие.
Также не стоит недооценивать возможностей самого Питона и его модулей. Тут вам можно и видео снять, CPU замерить и сообщение, куда надо, в случае чего отправить…
Что нам понадобится?
- Python 2.7 http://python.org/getit/
- pywinauto http://sourceforge.net/projects/pywinauto/
- виртуальная машина (по вкусу)
Еще рекомендую воспользоваться утилитой SWAPY, с помощью нее удобно смотреть свойства контролов, еще она генерирует код для pywinauto. Также с ее помощью можно проверить видит ли pywinauto контролы для вашего приложения или нет.
Спецификация теста
- Запускаем окно приложения.
- Кликаем на доступный контрол (закрываем окно).
- Проверяем fail criteria.
- Повторяем шаги 1 — 3 заданное время.
- По окончанию этого времени считать тест пройденным.
Fail criteria – условие, при котором тест считать проваленным. Например, запущено ли окно Crash report, не пингуется Интернет, и т.д. Тест также считать проваленным при любом эксепшене (непредвиденной ситуации).
Подводные камни
- Иногда окно нужно закрывать.
Пример: дочернее окно не имеет кнопки, которое его закроет и вернет фокус родительскому окну. - Окно может не обрабатывать Close – не фэйлить тест при этом.
Пример: диалоговое окно только с кнопками OK и Cancel. - Pywinauto может не видеть элементы управления на окне.
Тут мы ничего не сделаем – ищем другую связку. Проверяем через SWAPY или черезpywinauto.application.WindowSpecification.PrintControlIdentifiers
Код
По спецификации:
- Запускаем бинарник, ожидаем появления главного окна:
pywinauto.application.Application().start_(binary_path) pywinauto.timings.WaitUntil(WAIT_TIMEOUT, CHECK_INTERVAL, _check_window)
- С помощью
enabled_and_visible()
получаем список доступных контролов. Случайным образом выбираем по какому элементу кликнуть либо закрыть окно:
if ready_contr_list and random.randint(0,len(ready_contr_list)): control = random.choice(ready_contr_list) print('Click on - "%s"' % control.Texts()[0].encode('unicode-escape', 'replace')) highlight_control(control) control.Click() else: try: window.Close() except: pass else: print('Close window')
- Fail criteria. Ничего в голову не пришло. Ставим заглушку:
if 1==0: print('') result = TEST_FAILED break
Полный текст ниже. Хочется отметить еще несколько моментов:
make_action
пока умеет только посылать сигнал одинарного левого клика на контрол (или закрывать окно). Если тема будет интересна, можно будет усложнить логику.highlight_control
подсвечивает активный контрол. Просто красиво.- Для запуска скрипта понадобится указать:
- Путь к исполняемому файлу. Можно с параметрами:
BINARY_PATH = r'"C:pathapp.exe" –params 1 2 3'
- Регулярку для заголовка главного окна:
TITLE_RE = 'My app - .*'
- Класс главного окна. Смотрим в SWAPY:
CLASS_NAME = '#32770'
- Путь к исполняемому файлу. Можно с параметрами:
Результат
Тестировал родной Windows RDP клиент.
Буквально за час удалось поймать краш RDP клиента. Успех? — Наверно. Вручную повторить не удалось.
Тем не мене, дампы еще никто не отменял, так что вскрытие покажет…
Полный текст скрипта:
import pywinauto import random import thread import time import sys ''' GUI dynamic testing ''' TEST_FAILED = 1 TEST_PASSED = 0 TEST_EXEC_TIME = 60 * 60 WAIT_TIMEOUT = 30 CHECK_INTERVAL = 0.2 BINARY_PATH = r'"C:WINDOWSsystem32mstsc.exe"' TITLE_RE = 'Remote Desktop Connection' CLASS_NAME = '#32770' def _check_window(): ''' Check window is opened ''' try: pywinauto.findwindows.find_windows(title_re=TITLE_RE, class_name=CLASS_NAME)[0] except: return False else: return True def start_binary(binary_path): ''' Start a binary, wait for window opens ''' if not _check_window(): pywinauto.application.Application().start_(binary_path) pywinauto.timings.WaitUntil(WAIT_TIMEOUT, CHECK_INTERVAL, _check_window) return 0 def get_top_window(title_re, class_name): ''' Return the top window of the binary ''' if not _check_window(): start_binary(BINARY_PATH) app = pywinauto.application.Application() try: app.Connect_(title_re=TITLE_RE, class_name=CLASS_NAME) except pywinauto.findwindows.WindowAmbiguousError: app.Connect_(title_re=TITLE_RE, class_name=CLASS_NAME, active_only=True) return app.top_window_() def enabled_and_visible(all_conrt_list): ''' Return list of ready for action controls ''' ready_contr_list = [] for contr in all_conrt_list: if contr.IsEnabled() and contr.IsVisible(): ready_contr_list.append(contr) return ready_contr_list def highlight_control(control): ''' Highlight control ''' def _highlight_control(control, repeat = 1): while repeat > 0: repeat -= 1 control.DrawOutline(thickness=1) time.sleep(0.7) control.DrawOutline(colour=0xffffff, thickness=1) time.sleep(0.4) thread.start_new_thread(_highlight_control,(control,3)) return 0 def make_action(window): ''' Make action on a control or close a window ''' all_conrt_list = window.Children() ready_contr_list = enabled_and_visible(all_conrt_list) if ready_contr_list and random.randint(0,len(ready_contr_list)): control = random.choice(ready_contr_list) print('Click on - "%s"' % control.Texts()[0].encode('unicode-escape', 'replace')) highlight_control(control) control.Click() else: try: window.Close() except: pass else: print('Close window') def main(): ''' main section ''' start_time = time.time() result = -1 try: #start testig build start_binary(BINARY_PATH) #testing cycle while (time.time() - start_time) < TEST_EXEC_TIME: #get top window window = get_top_window(TITLE_RE, CLASS_NAME) #make an action make_action(window) #check fail criteria if 1==0: print('') result = TEST_FAILED break else: result = TEST_PASSED print('Test passed') except Exception, e: result = TEST_FAILED print('Test failed.n Exception %s' % e) sys.exit(result) if __name__ == '__main__': main()
Автор: moden