Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdf574ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
Влистинге 16!1 вы можете увидеть три объекта, чаще всего используемых Tester. Объект TSystem позволяет находить окна верхнего уровня, запускать приложения и приостанавливать тестирование. Объект TWindow, возвращаемый в листинге 16!1 методом FindTopTWindowByTitle, — главная рабочая лошадка и является оболочкой для HWND, включающей все виды свойств окна, которые вам могут понадобиться. Кроме того, TWindow умеет находить все дочерние окна, относящиеся к конкрет! ному родительскому окну. Последний объект в листинге 16!1 — объект TInput, поддерживающий единственный метод PlayInput для воспроизведения нажатий клавиш окну с фокусом.
Влистинге 16!2 представлен тест на языке VBScript, поясняющий работу с объектом TNotify. Одна из самых сложных проблем при разработке сценариев автоматизации тестирования связана с появлением неожиданного окна, такого как информационное окно ASSERT. Объект TNotify позволяет предоставить обработчик таких непредвиденных событий. Несложный сценарий, показанный в листинге 16!2, просто следит за любыми окнами, содержащими в заголовке слово «Notepad». Скорее всего класс TNotify вам понадобится нечасто, но порой он по!настоящему нужен.
Листинг 16-2. Сценарий HANDLERS.VBS демонстрирует использование объекта TNotify
'Тест VBScript, иллюстрирующий обработку оконных уведомлений
'Константы, передаваемые в метод TNotify.AddNotification.
Const antDestroyWindow |
= 1 |
Const antCreateWindow |
= 2 |
Const antCreateAndDestroy |
= 3 |
Const ansExactMatch |
= 0 |
Const ansBeginMatch |
= 1 |
Const ansAnyLocMatch |
= 2 |
' Получение системного объекта и объекта ввода.
Dim tSystem
Dim tInput
Set tSystem = WScript.CreateObject ( "Tester.TSystem" )
Set tInput = WScript.CreateObject ( "Tester.TInput" )
' Переменная для объекта TNotify.
Dim |
Notifier |
|
' Создание объекта TNotify. |
|
|
Set |
Notifier = _ |
|
|
WScript.CreateObject ( "Tester.TNotify" |
, _ |
|
"NotepadNotification" |
) |
'Добавление интересующих меня уведомлений. В данном случае мне
'нужны уведомления об уничтожении и создании окна. Все возможные
'комбинации уведомлений вы найдете в исходном коде TNotify.
ГЛАВА 16 Автоматизированное тестирование |
575 |
|
|
Notifier.AddNotification antCreateAndDestroy , _
ansAnyLocMatch |
, _ |
"Notepad"
'Запуск Notepad. tSystem.Execute "NOTEPAD.EXE"
'Пауза на 1 секунду. tSystem.Sleep 1.0
'Поскольку модель разделенных потоков небезопасна с точки зрения
'потоков, я использую в схеме уведомлений таймер. Однако сообщение
'может быть заблокировано, поскольку вся обработка ограничивается
'одним потоком. Эта функция позволяет вручную проверить уведомления
'о создании и уничтожении окна.
Notifier.CheckNotification
'Информационное окно процедуры NotepadNotification_CreateWindow
'вызывает блокировку, поэтому код завершения Notepad не будет
'выполнен, пока оно не будет закрыто.
tInput.PlayInput "%FX" tSystem.Sleep 1.0
'Еще одна проверка уведомлений. Notifier.CheckNotification
'Я даю TNotify шанс на перехват сообщения об уничтожении окна. tSystem.Sleep 1.0
'Отключение уведомлений. Если при работе с WSH этого
'не сделать, объект не будет уничтожен, и уведомления
'в таблице уведомлений останутся в активном состоянии. WScript.DisconnectObject Notifier
Set Notifier = Nothing
WScript.Quit
Sub NotepadNotificationCreateWindow ( tWin )
MsgBox ( "Notepad was created!!" )
End Sub
Sub NotepadNotificationDestroyWindow ( )
MsgBox ( "Notepad has gone away...." )
End Sub
Время от времени нужно вызывать метод CheckNotification объекта TNotify (при! чины этого я объясню в разделе «Реализация Tester»). Периодический вызов ме! тода CheckNotification гарантирует поступление уведомлений, даже если в выбранном
576 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
вами языке нет цикла сообщений. Листинг 16!2 иллюстрирует использование информационных окон (message box) в процедурах уведомлений, однако вам, вероятно, не захочется включать в реальные сценарии вызовы информационных окон, потому что они могут вызвать проблемы, неожиданно изменяя окно с фо! кусом.
Помните также, что я позволяю задать только ограниченное число уведомле! ний — пять, поэтому вам не следует использовать TNotify для общих задач сцена! риев, таких как ожидание появления окна сохранения файла. TNotify следует при! менять только для обработки неожиданных окон. Вы можете определить свои обработчики уведомлений и параметры поиска текста в заголовке окна так, что будете получать уведомления для окон, в которых вы не заинтересованы. Скорее всего вы будете получать нежелательные уведомления при использовании общих строк, таких как «Notepad», и указании, что строка может находиться в любом месте заголовка окна. Для избежания нежелательных уведомлений методу AddNotification объекта TNotify надо передавать как можно более специфичную информацию. Кроме того, в процедурах обработки события CreateWindow следует изучать полу! чаемый объект TWindow, чтобы можно было проверить, то ли это окно, которое вам нужно. В процедурах события DestroyWindow, обрабатывающих общие уведомления, следует выполнять поиск открытых окон, чтобы гарантировать, что интересую! щее вас окно больше не существует.
На CD есть и другие примеры, которые помогут вам лучше разобраться в ра! боте с Tester. NPAD_TEST.VBS — это более полный тест VBScript, включающий не! которые повторно используемые блоки. PAINTBRUSH.JS иллюстрирует воспроиз! ведение манипуляций с мышью, не зависящее от разрешения экрана. Его выпол! нение требует некоторого времени, однако результат того стоит. TesterTester — это основной блочный тест для COM!объекта Tester. Эта программа написана на C# и расположена в каталоге Tester\Tester\Tests\TesterTester. Изучив ее, вы получите представление о том, как использовать Tester вместе с .NET. Кроме того, пример TesterTester демонстрирует работу с объектом TWindows — коллекцией объектов
TWindow.
Хотя я предпочитаю писать свои блочные тесты на языках JScript и VBScript, я понимаю, что иногда это довольно трудно. Языки сценариев не позволяют конт! ролировать тип переменных и не включают редактор IntelliSense, такой как ре! дактор C# в Visual Studio .NET, поэтому вам придется вернуться к старому стилю отладки — «запустить и ошибиться». Языки сценариев нравятся мне в первую очередь тем, что они не требуют компиляции тестов. Если вы работаете с гибкой средой сборки программы, в которой легко создавать другие двоичные файлы в дополнение к главному приложению, вы можете применять .NET, создавая тесты вместе со сборкой своего приложения. Конечно, Tester не ограничивает вас про! стейшими языками тестирования. Если вам удобней писать тесты на C или Microsoft Macro Assembler (MASM) — пожалуйста.
Использовать объекты Tester довольно просто, однако реальная работа состо! ит в планировании тестов. Ваши тесты должны быть максимально конкретными и простыми. Когда я только приступал к автоматизации своих блочных тестов в начале карьеры, я пытался включить в них побольше функций. Теперь каждый мой сценарий тестирует только одну операцию. В качестве хорошего примера такого
ГЛАВА 16 Автоматизированное тестирование |
577 |
|
|
сценария можно привести тест открытия файла. Для повторного использования сценариев вы можете объединить их самыми разными способами. Например, как только вы напишете сценарий открытия файла, вы сможете включить его в три различных теста: тест открытия существующего файла, тест открытия несуществу! ющего файла и тест открытия поврежденного файла. Как и при разработке обычных программ, следует избегать включения в тесты жестко закодированных строк. Это не только облегчит интернационализацию сценария, но и упростит его адапта! цию к очередной версии системы меню и комбинаций управляющих клавиш (accelerator).
При разработке сценариев Tester нужно предусмотреть и способы проверки того, что сценарии на самом деле работают. Если вам нечем заняться, можете просто сидеть и следить за их выполнением, сравнивая результаты каждого запуска. Од! нако лучше было бы записывать основные состояния и точки сценария, чтобы сравнение результатов можно было проводить автоматически. Если для выполне! ния сценариев вы используете CSCRIPT.EXE, можете перенаправить вывод в файл методом WScript.Echo. По завершении сценария вы можете сравнить разные вер! сии полученных файлов утилитой нахождения различий версий (такой как WinDiff) и узнать, корректно ли выполнился сценарий. Помните: записываемая информа! ция должна быть нормализованной и не зависящей от деталей конкретного за! пуска сценария. Так, если вы работаете над приложением, загружающим из сети курсы акций, не следует записывать в файл время последнего обновления курса.
Что сказать об отладке сценариев Tester? Tester не включает собственного ин! тегрированного отладчика, поэтому вам понадобятся другие средства отладки, до! ступные для языка, на котором написан сценарий. Отлаживая сценарий, старай! тесь не прерываться на вызове метода PlayInput объекта Tinput, потому что при остановке на этом методе нажатия клавиш будут воспроизведены неправильному окну. Для решения этой потенциальной проблемы я обычно перед каждым вызо! вом PlayInput перемещаю окно, которому посылаю нажатия клавиш, на вершину z!порядка, вызывая метод SetForegroundTWindow объекта TWindow. Это позволяет мне прерваться на вызове SetForegroundTWindow и проверить состояние приложения, не вызывая ошибок воспроизведения нажатий клавиш.
Запись сценариев
Теперь вы понимаете работу объектов Tester и знаете, как их вызывать из собствен! ных сценариев, поэтому я хочу перейти к рассмотрению программы TESTREC.EXE, которую вы будете применять для записи взаимодействия со своими приложени! ями. Запустив TESTREC.EXE в первый раз, вы заметите, что это текстовый редак! тор, который уже при запуске генерирует некоторый объем кода. По умолчанию сценарии создаются на языке JScript, но чуть ниже я покажу, как изменить его на VBScript. Для начала записи нужно нажать на панели инструментов кнопку Record (запись) или клавиши Ctrl+R.
При записи сценария TESTREC.EXE минимизируется и изменяет заголовок на «RECORDING!», давая вам знать о происходящем. Остановить запись можно несколь! кими способами. Самый простой — сделать TESTREC.EXE активной программой, нажав Alt+Tab или выбрав ее на панели задач. Запись сценария также остановит! ся при нажатии Ctrl+Break или Ctrl+Alt+Delete; первый вариант упоминается в
578 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
документации к функциям!ловушкам, а второй позволяет принудительно завер! шить все активные ловушки записи журнала (journaling hooks), при помощи ко! торых TESTREC.EXE колдует.
Прежде чем приступить к записи своих многочисленных сценариев, вы долж! ны составить некоторый план, чтобы полностью задействовать преимущества Tester. Хотя Tester умеет обрабатывать и воспроизводить манипуляции с мышью, ваши сценарии будут гораздо более надежными, если все действия вы будете выполнять при помощи клавиатуры. Одно из достоинств Tester в том, что при записи сцена! рия он внимательно следит за тем, какому окну принадлежит фокус. По умолча! нию перед обработкой одиночных и двойных щелчков мыши Tester генерирует код, устанавливающий фокус на окно верхнего уровня. Кроме того, при записи действий с клавиатурой Tester отслеживает комбинацию Alt+Tab, также устанав! ливая фокус.
Так как запись всех событий мыши может привести к получению просто ог! ромного сценария, по умолчанию TESTREC.EXE записывает только одиночные, двойные щелчки и перемещение курсора на каждые 50 пикселов при нажатой клавише. Конечно, я позволяю точно указать параметры записи сценариев. Диа! логовое окно Script Recording Options (параметры записи сценариев) программы TESTREC.EXE, показанное на рис. 16!1, доступно при нажатии Ctrl+T или выборе пункта Script Options (параметры сценария) из меню Scripts (сценарии). На ри! сунке все пункты имеют значения по умолчанию.
Рис. 16 1. Параметры записи сценариев Tester
В самой верхней части окна Script Recording Options вы можете выбрать язык записи новых сценариев: JScript или VBScript. Установленный по умолчанию флажок Record For Multiple Monitor Playback (запись сценария для воспроизведения на нескольких мониторах), включает в сценарий вызовы метода TSystem.CheckVirtual Resolution, настраивающие размер экрана для последующих записываемых дей! ствий. Если этот флажок убрать, при нажатии кнопок мыши и доступе к точкам вне основного монитора запись будет прерываться. Возможно, вам следует отклю! чить эту функцию, если вы планируете запускать записанные сценарии на несколь!
580 |
ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода |
|
|
|
|
Табл. 16-1. |
Команды мыши … (продолжение) |
|
|
|
|
Команда |
Использование |
|
CTRL |
DOWN |
{CTRL DOWN} |
CTRL |
UP |
{CTRL UP} |
ALT |
DOWN |
{ALT DOWN} |
ALT |
UP |
{ALT UP} |
|
|
btn: LEFT, RIGHT, MIDDLE |
x: координата экрана X y: координата экрана Y
Есть некоторые функции мыши, которые я не смог реализовать. Во!первых, это обработка колесика. Я использовал ловушку записи журнала для перехвата дей! ствий с клавиатурой и мышью и смог получать сообщения о вращении колесика. Но, увы, из!за ошибки в функции!ловушке нет возможности узнать направление вращения колесика. Во!вторых, я не смог реализовать обработку новых клавиш X1 и X2, имеющихся на мыши Microsoft Explorer. Соответствующие сообщения WM_XBUTTON* содержат данные о нажатой клавише в старшем слове wParam. Так как сообщение WM_MOUSEWHEEL хранит направление вращения колесика там же, но фун! кция!ловушка эту информацию не получает, я сомневаюсь, чтобы в случае кноп! ки X ситуация чем!нибудь отличалась.
Реализация Tester
Вы уже представляете, как записывать и воспроизводить при помощи Tester сцена! рии автоматизации тестирования, поэтому я перейду к некоторым более существен! ным вопросам его реализации. Просуммировав объем исходных и двоичных фай! лов Tester, включая TESTER.DLL и TESTREC.EXE, вы увидите, что Tester — самая крупная, а кроме того, и самая сложная утилита в этой книге из!за COM, рекур! сивного синтаксического разбора и фоновых таймеров.
Уведомления и воспроизведение файлов в TESTER.DLL
В первом издании книги я реализовал TESTER.DLL на Visual Basic 6, потому что в то время язык и среда разработки Visual Basic 6 пользовались при работе с COM наибольшей популярностью. Однако я не хотел требовать от вас установки Visual Basic 6 только для компиляции одной DLL для COM. Прежде всего я решил пере! нести код TESTER.DLL на платформу .NET. Так как модуль воспроизведения собы! тий клавиатуры был написана на C++, я решил, что будет проще переписать часть Tester, реализованную на Visual Basic 6, на C++, задействовав при этом преимуще! ства новой технологии программирования COM на базе атрибутов (attributed COM programming).
В целом модель COM на базе атрибутов очень удобна, но мне потребовалось некоторое время для обнаружения атрибута idl_quote, необходимого для поддер! жки объявлений интерфейсов. Очень приятным сюрпризом при работе с COM на базе атрибутов оказалась чистота комбинирования языков IDL/ODL и кода C++. Кроме того, благодаря значительно улучшенным мастерам облегчилось добавле!
ГЛАВА 16 Автоматизированное тестирование |
581 |
|
|
ние интерфейсов и их методов и свойств. Я хорошо помню, сколько времени ухо! дило на решение проблем с мастерами в предыдущих версиях Visual Studio.
Когда я только задумывал утилиту автоматизированного воспроизведения, я полагал, что могу использовать функцию SendKeys языка Visual Basic 6. Однако после тестирования я обнаружил, что такая реализация неудовлетворительна, так как она некорректно посылает события клавиатуры таким программам, как Microsoft Outlook. Это означало, что мне нужно было написать собственную функцию, ко! торая правильно посылала бы события клавиатуры и позволяла в будущем реали! зовать воспроизведение событий мыши. К счастью, я натолкнулся на функцию SendInput, поддерживаемую технологией Microsoft Active Accessibility (MSAA), и заменил ею все предыдущие низкоуровневые функции обработки событий, такие как keybd_event. Кроме того, функция SendInput помещает всю вводимую инфор! мацию в поток ввода клавиатуры или мыши в виде непрерывного блока, гаранти! руя, что вводимые вами данные не будут перемешаны с посторонней пользова! тельской информацией. Эта возможность была особенно привлекательной для Tester.
Как только я узнал, как правильно посылать нажатия клавиш, мне нужно было разработать формат их ввода. Функция SendKeys языка Visual Basic 6 или класс System.Windows.Forms.SendKeys платформы .NET уже предоставляли отличный фор! мат ввода, поэтому я решил воспроизвести его в своей функции PlayInput. Я ис! пользовал все, кроме кода повтора клавиш, и, как уже говорилось, расширил формат, включив в него поддержку воспроизведения событий мыши. В коде синтаксичес! кого разбора нет ничего особо интересного, но если вам захочется его изучить, вы найдете его на CD в файле Tester\Tester\ParsePlayInputString.CPP. Если вам за! хочется увидеть его в действии, можете проработать в отладчике программу Parse! PlayKeysTest из каталога Tester\Tester\Tests\ParsePlayKeysTest. Как можно догадать! ся по имени программы, это один из блочных тестов для Tester DLL.
Объекты TWindow, TWindows и TSystem просты, и вы сможете разобраться в их ра! боте по исходному коду. Эти три класса являются по сути оболочками для соот! ветствующих API!функций Windows. Единственный более!менее интересный ас! пект их реализации заключался в написании кода, гарантирующего, что методы
TWindow.SetFocusTWindow и TSystem.SetSpecificFocus могут сделать окно активным. Для этого они до установки фокуса выполняют присоединение к потоку вывода при помощи API!функции AttachThreadInput.
С некоторыми интересными проблемами я столкнулся при написании класса TNotify. Когда я только начал думать о том, что нужно для определения создания или уничтожения окна с конкретным заголовком, я не ожидал, что создать такой класс будет настолько трудно. Кроме того, я обнаружил, что уведомления о создании окон невозможно сделать надежными, не приложив героических усилий.
Моя первая идея заключалась в реализации системной ловушки для приложе! ний компьютерной профессиональной подготовки [computer!based training (CBT) hook]. В документации SDK подразумевается, что ловушка CBT — лучший метод перехвата событий создания и уничтожения окон. Я быстро написал пример, но вскоре столкнулся с неприятностями. Когда моя ловушка получала уведомление HCBT_CREATEWND, я не всегда мог узнать заголовок окна. По непродолжительном раз!
582 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
мышлении проблема начала обретать смысл: вероятно, ловушка CBT вызывалась во время обработки сообщения WM_CREATE, и очень немногие окна имели в этот момент установленные заголовки. Единственным типом окон, заголовки которых я мог надежно получать при помощи уведомления HCBT_CREATEWND, были диалого! вые окна. В то же время с уничтожением окон при использовании ловушки CBT все было в порядке.
Просмотрев остальные типы ловушек, я расширил свой пример и попробовал их в действии. Как я и подозревал, простое отслеживание WM_CREATE не обеспечи! вало надежного получения заголовка окна. Один друг предложил мне наблюдать только за сообщениями WM_SETTEXT, так как именно его в конечном счете исполь! зуют для установки заголовка почти все окна. Конечно, если вы рисуете в некли! ентской области окна или выполняете битовый перенос (bit blitting), вы не буде! те использовать сообщение WM_SETTEXT. По ходу дела я заметил одну интересную деталь: некоторые программы, в частности Microsoft Internet Explorer, посылают сообщения WM_SETTEXT с одним и тем же текстом много раз подряд.
Поняв, что мне нужно следить за сообщениями WM_SETTEXT, я внимательней рассмотрел типы ловушек, которые мог установить. В конце концов наилучшим вариантом оказалась ловушка вызова оконной процедуры (WH_CALLWNDPROCRET). Она позволяет с легкостью наблюдать за сообщениями WM_CREATE и WM_SETTEXT, а также за сообщениями WM_DESTROY. Сначала я полагал, что с WM_DESTROY будут некоторые проблемы, так как думал, что заголовок окна может быть уничтожен до получе! ния этого сообщения. К счастью, оказалось, что заголовок окна корректен вплоть до получения сообщения WM_NCDESTROY.
Рассмотрев плюсы и минусы обработки сообщений WM_SETTEXT только для тех окон, которые еще не имеют заголовка, я решил поступить проще и обрабаты! вать все сообщения WM_SETTEXT. Альтернативным вариантом могло бы быть созда! ние конечного автомата для отслеживания созданных окон и времени установки ими своих заголовков; это решение казалось безошибочным, однако в то же вре! мя сложным в реализации. Недостаток обработки всех сообщений WM_SETTEXT в том, что вы можете получить много уведомлений о создании одного окна. Например, если вы установите обработчик TNotify для окон, содержащих в заголовке слово «Notepad», вы будете получать уведомления не только при запуске NOTEPAD.EXE, но и при каждом открытии им нового файла. В итоге я предпочел смириться с не самой лучшей реализацией, но не проводить многие дни за отладкой «правиль! ного» решения. Кроме того, написание ловушки охватывало реализацию итого! вого класса TNotify только на четверть; остальные три четверти были посвящены уведомлению пользователя о создании и уничтожении окон.
Выше я упоминал, что использование объекта TNotify связано с некоторыми неудобствами: время от времени вы должны вызывать метод CheckNotification. Необходимость периодического вызова CheckNotification объясняется тем, что Tester поддерживает только модель разделенных потоков, которая не может быть мно! гопоточной; так что мне нужен был механизм проверки создания и уничтожения окон, работающий в том же потоке, что и оставшаяся часть Tester.
Рассмотрев некоторые аспекты механизма уведомлений, я ограничил требо! вания следующими базовыми факторами.