Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004

.pdf
Скачиваний:
353
Добавлен:
13.08.2013
Размер:
3.3 Mб
Скачать

ГЛАВА 15

Блокировка в многопоточных приложениях

563

 

 

 

 

 

 

 

 

 

}

\

 

HOOKFN_EPILOG ( iNumParams ) ;

 

/*//////////////////////////////////////////////////////////////////////

 

// Объявление соглашения вызова для всех функций DD_*.

 

//////////////////////////////////////////////////////////////////////*/

 

#define NAKEDDEF __declspec(naked)

 

/*//////////////////////////////////////////////////////////////////////

//ВАЖНОЕ ПРИМЕЧАНИЕ! ВАЖНОЕ ПРИМЕЧАНИЕ!

//Следующие прототипы выглядят, как функции с соглашением __cdecl, но на

//самом деле это не так: они все __stdcall! Использование правильного

//соглашения вызова гарантируется моими прологом и эпилогом!

//////////////////////////////////////////////////////////////////////*/

////////////////////////////////////////////////////////////////////////

//Функции, перехват которых обязателен, иначе система не будет

//работать.

BOOL DD_FreeLibrary ( HMODULE hModule ) ;

 

 

VOID DD_FreeLibraryAndExitThread (

HMODULE

hModule

,

 

 

DWORD

dwExitCode

) ;

HMODULE DD_LoadLibraryA (

LPCSTR lpLibFileName ) ;

 

HMODULE DD_LoadLibraryW (

LPCWSTR lpLibFileName ) ;

 

HMODULE DD_LoadLibraryExA

( LPCSTR

lpLibFileName ,

 

 

HANDLE

hFile

,

 

 

DWORD

dwFlags

) ;

 

HMODULE DD_LoadLibraryExW

( LPCWSTR

lpLibFileName ,

 

 

HANDLE

hFile

,

 

 

DWORD

dwFlags

) ;

 

VOID DD_ExitProcess ( UINT uExitCode ) ;

FARPROC DD_GetProcAddress ( HMODULE hModule , LPCSTR lpProcName ) ;

////////////////////////////////////////////////////////////////////////

// Функции работы с потоками

 

 

HANDLE DD_CreateThread (LPSECURITY_ATTRIBUTES

lpThreadAttributes

,

 

DWORD

dwStackSize

,

 

LPTHREAD_START_ROUTINE

lpStartAddress

,

 

LPVOID

lpParameter

,

 

DWORD

dwCreationFlags

,

 

LPDWORD

lpThreadId

) ;

VOID DD_ExitThread ( DWORD dwExitCode ) ;

 

 

DWORD DD_SuspendThread ( HANDLE hThread ) ;

 

 

DWORD DD_ResumeThread (

HANDLE hThread ) ;

 

 

BOOL DD_TerminateThread

( HANDLE hThread , DWORD dwExitCode ) ;

 

//Ниже приведены функции работы с потоками стандартной библиотеки C.

//Они обрабатываются правильно, так как используют соглашение __cdecl. uintptr_t

см. след. стр.

564

ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

DD_beginthreadex ( void *

 

security

 

,

 

 

 

unsigned

 

stack_size

,

 

 

 

unsigned ( __stdcall *start_address )( void * ) ,

 

void *

 

arglist

 

,

 

 

 

unsigned

 

initflag

 

,

 

 

 

unsigned *

thrdaddr

 

) ;

 

 

uintptr_t

 

 

 

 

 

 

 

 

DD_beginthread ( void( __cdecl *start_address )( void * ) ,

 

 

unsigned

stack_size

 

 

 

,

 

 

void *

arglist

 

 

 

 

) ;

VOID DD_endthreadex ( unsigned retval ) ;

 

 

 

 

 

VOID DD_endthread ( void ) ;

 

 

 

 

 

 

 

////////////////////////////////////////////////////////////////////////

// Функции ожидания и специальные функции

 

 

 

 

 

DWORD DD_WaitForSingleObject ( HANDLE

hHandle

 

,

 

 

 

 

 

DWORD

dwMilliseconds

) ;

 

 

DWORD DD_WaitForSingleObjectEx ( HANDLE hHandle

 

,

 

 

 

 

DWORD

dwMilliseconds ,

 

 

 

 

BOOL

bAlertable

) ;

 

 

DWORD DD_WaitForMultipleObjects( DWORD

 

 

nCount

 

 

,

 

 

CONST HANDLE * lpHandles

 

,

 

 

BOOL

 

 

bWaitAll

 

,

 

 

DWORD

 

 

dwMilliseconds

) ;

DWORD DD_WaitForMultipleObjectsEx( DWORD

 

nCount

 

,

 

 

CONST HANDLE * lpHandles

 

,

 

 

BOOL

 

bWaitAll

 

,

 

 

DWORD

 

dwMilliseconds ,

 

 

BOOL

 

bAlertable

 

) ;

DWORD DD_MsgWaitForMultipleObjects ( DWORD

nCount

 

,

 

 

 

LPHANDLE

pHandles

 

,

 

 

 

BOOL

fWaitAll

 

,

 

 

 

DWORD

dwMilliseconds,

 

 

 

DWORD

dwWakeMask

) ;

DWORD DD_MsgWaitForMultipleObjectsEx ( DWORD

 

nCount

 

 

,

 

 

 

LPHANDLE

pHandles

 

,

 

 

 

DWORD

 

dwMilliseconds

,

 

 

 

DWORD

 

dwWakeMask

 

,

 

 

 

DWORD

 

dwFlags

 

) ;

DWORD DD_SignalObjectAndWait ( HANDLE hObjectToSignal ,

 

 

 

 

HANDLE

hObjectToWaitOn

,

 

 

 

 

DWORD

dwMilliseconds

,

 

 

 

 

BOOL

bAlertable

 

) ;

 

 

BOOL DD_CloseHandle ( HANDLE hObject ) ;

 

 

 

 

 

////////////////////////////////////////////////////////////////////////

// Функции работы с критическими секциями

 

 

 

 

 

VOID DD_InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

BOOL DD_InitializeCriticalSectionAndSpinCount (

 

 

 

 

 

 

LPCRITICAL_SECTION lpCriticalSection,

 

 

 

 

 

 

 

 

 

ГЛАВА 15 Блокировка в многопоточных приложениях

565

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

DWORD

 

dwSpinCount

);

 

VOID DD_DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection ) ;

 

VOID DD_EnterCriticalSection ( LPCRITICAL_SECTION lpCriticalSection ) ;

 

VOID DD_LeaveCriticalSection ( LPCRITICAL_SECTION lpCriticalSection ) ;

 

DWORD DD_SetCriticalSectionSpinCount (

 

 

 

 

 

 

LPCRITICAL_SECTION lpCriticalSection,

 

 

 

DWORD

 

dwSpinCount

);

 

BOOL DD_TryEnterCriticalSection ( LPCRITICAL_SECTION lpCriticalSection);

 

////////////////////////////////////////////////////////////////////////

 

// Функции работы с мьютексами

 

 

 

 

 

 

HANDLE DD_CreateMutexA ( LPSECURITY_ATTRIBUTES lpMutexAttributes

,

 

BOOL

 

 

bInitialOwner

,

 

LPCSTR

 

lpName

) ;

 

HANDLE DD_CreateMutexW ( LPSECURITY_ATTRIBUTES lpMutexAttributes

,

 

BOOL

 

 

bInitialOwner

,

 

LPCWSTR

 

lpName

) ;

 

HANDLE DD_OpenMutexA ( DWORD

dwDesiredAccess

,

 

 

 

BOOL

bInheritHandle

,

 

 

 

LPCSTR lpName

) ;

 

 

 

HANDLE DD_OpenMutexW ( DWORD

dwDesiredAccess

,

 

 

 

BOOL

bInheritHandle

,

 

 

 

LPCWSTR lpName

) ;

 

 

BOOL DD_ReleaseMutex ( HANDLE hMutex ) ;

 

 

 

 

////////////////////////////////////////////////////////////////////////

 

// Функции работы с семафорами

 

 

 

 

 

 

HANDLE

 

 

 

 

 

 

DD_CreateSemaphoreA ( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes ,

 

LONG

 

 

lInitialCount

,

 

LONG

 

 

lMaximumCount

,

 

LPCSTR

 

lpName

);

 

HANDLE

 

 

 

 

 

 

DD_CreateSemaphoreW ( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes ,

 

LONG

 

 

lInitialCount

,

 

LONG

 

 

lMaximumCount

,

 

LPCWSTR

 

lpName

);

 

HANDLE DD_OpenSemaphoreA ( DWORD

dwDesiredAccess

,

 

 

BOOL

bInheritHandle

,

 

 

LPCSTR lpName

 

) ;

 

 

HANDLE DD_OpenSemaphoreW ( DWORD

dwDesiredAccess

,

 

 

BOOL

bInheritHandle

,

 

 

LPCWSTR lpName

 

) ;

 

 

BOOL DD_ReleaseSemaphore ( HANDLE

hSemaphore

 

,

 

 

LONG

lReleaseCount

,

 

 

LPLONG lpPreviousCount

) ;

 

 

////////////////////////////////////////////////////////////////////////

 

// Функции работы с событиями

 

 

 

 

 

 

HANDLE DD_CreateEventA ( LPSECURITY_ATTRIBUTES lpEventAttributes ,

 

 

 

 

 

 

 

 

см. след. стр.

566

ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BOOL

 

bManualReset

,

 

 

 

 

BOOL

 

bInitialState

,

 

 

 

 

LPCSTR

lpName

) ;

 

 

HANDLE DD_CreateEventW

( LPSECURITY_ATTRIBUTES

lpEventAttributes

,

 

 

 

 

BOOL

 

bManualReset

,

 

 

 

 

BOOL

 

bInitialState

,

 

 

 

 

LPCWSTR

lpName

) ;

 

 

HANDLE DD_OpenEventA (

DWORD

dwDesiredAccess

,

 

 

 

 

 

BOOL

bInheritHandle

,

 

 

 

 

 

LPCSTR lpName

) ;

 

 

 

HANDLE DD_OpenEventW (

DWORD

dwDesiredAccess

,

 

 

 

 

 

BOOL

bInheritHandle

,

 

 

 

 

 

LPCWSTR lpName

) ;

 

 

 

BOOL DD_PulseEvent ( HANDLE hEvent ) ;

 

 

 

 

BOOL DD_ResetEvent ( HANDLE hEvent ) ;

 

 

 

 

BOOL DD_SetEvent ( HANDLE hEvent ) ;

 

 

 

 

#endif

// _DD_FUNCS_H

 

 

 

 

 

 

 

 

 

 

 

 

 

В первой версии DeadlockDetection я допустил глупейшую ошибку «наследо! вания при редактировании». Я создал функции!ловушки для LoadLibraryA и LoadLib raryW и понял, что мне также нужны ловушки для LoadLibraryExA и LoadLibraryExW. Как типичный программист, я вырезал функции LoadLibraryA/W, вставил их в нуж! ное место кода и отредактировал с учетом особенностей LoadLibraryExA/W. Если вы посмотрите на макрос HOOKFN_EPILOG, то увидите, что он принимает некоторое значение, а именно число параметров функциии. Наверное, вы уже догадались, что случилось: я забыл изменить это значение с 1 на 3, поэтому вызовы LoadLibrary ExA/W удаляли из стека два лишних элемента и приводили к краху программ!

Изучив код всех функций!ловушек, я понял, что он по сути везде одинаков. Для инкапсуляции общих действий я создал макросы HOOKFN_STARTUP и HOOKFN_SHUTDOWN. Как видно по имени, макрос HOOKFN_STARTUP размещается в начале функции!ловушки и заботится о прологе, а также выполняет необходимое протоколирование до вызова действительной функции. Он принимает следующие параметры: перечис! ление функции, флаг DDOPT_*, показывающий, к какой группе относится данная функция, и булев флаг, выполняющий предварительное протоколирование, если имеет значение TRUE. Предварительное протоколирование предназначено для функций, которые потенциально могут вызывать блокировку, таких как WaitFor SingleObject. Макрос HOOKFN_SHUTDOWN принимает число параметров функции и тот же флаг DDOPT_*, что передается в HOOKFN_STARTUP. Конечно, чтобы не допустить ту же ошибку, которую я сделал в случае ловушек LoadLibraryExA/W, я проверил число параметров HOOKFN_SHUTDOWN.

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

ГЛАВА 15 Блокировка в многопоточных приложениях

567

 

 

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

Во!вторых, DeadlockDetection перехватывает функции при загрузке DLL в вашу программу с помощью LoadLibrary. Однако DeadlockDetection может получить контроль только после выполнения в DLL функции DllMain, поэтому, если какие! то объекты синхронизации создаются или используются во время DllMain, Deadlock! Detection может их упустить.

В!третьих, DeadlockDetection также перехватывает функции GetProcAddress и ExitProcess. Перехват GetProcAddress полезен, если ваша программа или элемент управления сторонней фирмы, который может привести к блокировке, вызывает GetProcAddress для нахождения метода синхронизации в период выполнения.

Я перехватываю ExitProcess потому, что при завершении приложения мне нужно отменить перехват функций и завершить DeadlockDetection, чтобы она не вызва! ла крах или зависание вашей программы. Так как при завершении программы контролировать порядок выгрузки DLL невозможно, вы с легкостью можете по! пасть в ситуацию, когда DLL утилиты DeadlockDetection, такая как DeadDetExt, выгружается до самой DeadlockDetection. К счастью, очень немногие программи! сты обрабатывают несколько потоков после вызова ExitProcess.

Перехват ExitProcess реализован в файле DEADLOCKDETECTION.CPP. Из!за ог! ромной важности правильного завершения DeadlockDetection я принудительно перехватываю все вызовы ExitProcess даже в игнорируемых модулях. Это позво! ляет исключить неожиданные ошибки, при которых функции синхронизации остаются перехваченными после завершения DeadlockDetection.

Наконец, вместе с DeadlockDetection вы можете найти на CD несколько тесто! вых программ. Все они включены в главное решение DeadlockDetectionTests и компонуются вместе с DEADLOCKDETECTION.DLL. Они помогут вам понять работу DeadlockDetection.

Что после DeadlockDetection?

DeadlockDetection — достаточно полная утилита, и я успешно применял ее для отслеживания многих многопоточных блокировок. Однако, как всегда, я предла! гаю вам обдумать возможности расширения DeadlockDetection, чтобы сделать ее еще полезнее. Вот некоторые мои идеи по этому поводу.

Создайте отдельное приложение для работы с файлом DEADLOCKDETECTI! ON.INI. Будет еще лучше, если оно позволит указывать DLL DeadDetExt и будет проверять, что эта DLL экспортирует корректные функции.

Вы можете оптимизировать функции!ловушки, если они не выполняют про! токолирования. В этом случае нужно копировать не все значения регистров.

На данный момент DeadlockDetection просто пропускает перехват некоторых DLL, которые ей указаны. Было бы великолепно, если б вы разработали меха! низм пропуска DLL с учетом выполняемой программы.

568 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

Отладка: фронтовые очерки

Проблема фиксации транзакций при использовании пула объектов COM

Боевые действия

Мой хороший друг Питер Иерарди (Peter Ierardi) рассказал об одной инте! ресной многопоточной ошибке. Он работал над крупным проектом DCOM, включавшим многопоточную службу DCOM для координации транзакций базы данных. Служба DCOM управлял транзакциями, создавая пул ориен! тированных на базу данных внутрипроцессных COM!объектов, применяв! шихся для записи и чтения данных из реляционной СУБД. Взаимодействие компонентов осуществлялось при помощи Microsoft Message Queue Server (MSMQ). Несмотря на явную фиксацию транзакций, данные, похоже, не за! писывались в базу. Служба DCOM повторяла попытку от трех до пяти раз, и только после этого данные появлялись, как по щучьему велению. Очевид! но, лишние попытки снижали быстродействие приложения, а то, что дан! ные не хотели записываться в базу данных, вызывал тревогу.

Исход

После нескольких тяжелых сеансов отладки Питер обнаружил, что служба DCOM выполнял чтение/запись при помощи отдельных несинхронизиро! ванных потоков. Чтение происходило до того, как отдельный экземпляр COM!объекта базы записывал данные. Во время отладки это поведение было далеко не очевидным, потому что отладчик навязывал правильный отсчет времени и синхронизацию. В конце концов Питер обнаружил проблему, правильно отметив объекты в журнале событий.

Полученный опыт

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

Ошибка Питера состояла в том, что MSMQ выполнял операции чтения/ записи, как легко догадаться, гораздо быстрее, чем база данных. Хотя Пи! тер и другие члены его группы тщательно проработали и спланировали все многопоточные фрагменты, они все же не до конца осознавали, насколько быстрее в реальном мире будут выполняться определенные операции, не подвластные их приложению.

ГЛАВА 15 Блокировка в многопоточных приложениях

569

 

 

Резюме

Разрабатывать многопоточные приложения трудно, и в этой области встречают! ся одни из самых сложных ошибок. В данной главе я обсудил методы, которые помогут вам избегать блокировок с самого начала проекта. Как я подчеркнул в начале главы, при программировании многопоточного приложения особую важ! ность приобретает заблаговременное планирование, поэтому, приступая к рабо! те над такой программой, вы должны предоставить своей группе достаточно вре! мени и ресурсов для тщательного и правильного ее проектирования. Однако, когда вы столкнетесь с неизбежными многопоточными блокировками, не нужно пани! ковать — в этой главе я представил утилиту DeadlockDetection, которая скажет вам, какие потоки заблокированы на каком объекте синхронизации.

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

Г Л А В А

16

Автоматизированное

тестирование

В главе 3 я рассказал про блочные тесты и объяснил, почему они так важны для разработки высококачественного кода. Если вы работаете преимущественно над внутренней логикой приложения, блочные тесты могут быть довольно просты. На CD в каталоге BugslayerUtil\Tests вы можете найти все блочные тесты, использо! ванные мной при разработке BUGSLAYERUTIL.DLL. Почти все они — консольные приложения, прекрасно справляющиеся со своими обязанностями.

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

Проклятие блочного тестирования: UI

Абсолютно уверен, что если разработчики программ для Microsoft Windows и получают туннельный синдром, то это вызвано не написанием исходного кода, а многократными нажатиями одних и тех же комбинаций клавиш при тестирова! нии приложений. После пятитысячного нажатия Alt+F, O запястья сковываются сильнее, чем арматура, залитая бетоном. При отсутствии средства, автоматизиру! ющего доступ к различным функциям ваших приложений, обычно необходимо следовать некоторому сценарию, чтобы гарантировать, что блочное тестирова!

ГЛАВА 16 Автоматизированное тестирование

571

 

 

ние проведено в достаточном объеме. Тестирование программ вручную при по! мощи сценариев мгновенно надоедает, значительно повышая вероятность чело! веческих ошибок.

Автоматизация блочного тестирования позволяет уменьшить объем работы с клавиатурой и дает возможность быстрой проверки состояния кода. Очень жаль, но аналога программы Recorder из состава Microsoft Windows 3.0 и 3.1 в 32!раз! рядных ОС нет. Если вы не работали со старыми версиями Windows, я поясню сказанное: Recorder записывала ваши манипуляции с мышью и клавиатурой в файл, который позднее можно было воспроизвести, сымитировав физические события мыши и клавиатуры. В настоящее время доступны некоторые программы сторонних фирм, обеспечивающие автоматизацию работы с приложением и другие возмож! ности (например, сравнение экранов с проверкой каждого пиксела и поддержку базы данных о времени проведения тех или иных тестов), но я все равно хотел разработать что!то более простое и дружественное к программистам. Так роди! лась идея Tester.

Задумав утилиту автоматизации тестирования, я некоторое время размышлял о том, какие именно возможности мне хотелось бы получить. Сначала я решил разработать утилиту вроде Recorder. Во времена Windows 3.0 у меня был целый набор REC!файлов для выполнения моих тестов. Однако Recorder имел большой недостаток: он не поддерживал условные тесты. Если во время тестирования мое приложение сообщало об ошибке, Recorder просто продолжал работу, проигры! вая записанные нажатия клавиш клавиатуры и мыши и полностью игнорируя стра! дания моей программы. Однажды благодаря Recorder я умудрился уничтожить половину ОС: я тестировал собственное расширение WINFILE.EXE, и, когда в нем возникла ошибка, Recorder проиграл команду удаления файлов для всего катало! га System. Мое новое средство автоматизации тестирования непременно должно было поддерживать конструкцию if...then...else.

Очевидно, что для этого мне нужен был некоторый вид языка. Разработка соб! ственного языка тестирования казалась заманчивым интеллектуальным упражне! нием, но вскоре я пришел к выводу, что я больше заинтересован в полезном от! ладочном средстве, а не в проектировании языка и работе с YACC и FLEX. Я почти сразу понял, что Tester нужно реализовать как объект COM: благодаря этому про! граммисты могли бы создавать тесты на предпочтительном для них языке, а я мог бы сосредоточиться на программировании функций регрессивного тестирования, а не на разработке нового языка. Лично я предпочитаю создавать тесты на язы! ках сценариев, таких как Microsoft Visual Basic Scripting Edition (VBScript) и Microsoft JScript, потому что они не требуют компиляции. Однако различные реализации механизма сценариев Microsoft Windows Scripting Host (WSH) имеют некоторые ограничения, на которые я укажу ниже. Сейчас я хотел бы обсудить требования, которыми я руководствовался при создании Tester.

Требования к Tester

Я хотел, чтобы Tester очень хорошо делал две вещи: записывал нажатые вами ком! бинации клавиш и проигрывал их обратно вашему приложению, ускоряя прове! дение блочного тестирования. Если вы когда!либо изучали коммерческие сред!

572 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода

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

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

2.При получении строки нажатых клавиш в формате, используемом классом System.Windows.Forms.SendKeys, Tester должен уметь проигрывать ее активному окну.

3.Tester должен поддерживать возможность нахождения любых окон верхнего уровня или дочерних окон по их заголовку или классу.

4.При получении любого HWND Tester должен быть способен получить все свой! ства окна.

5.Tester должен уведомлять пользовательский сценарий о создании/уничтоже! нии конкретного окна, чтобы сценарий мог обработать потенциальные усло! вия ошибки или выполнить дополнительную обработку окна. Tester не должен ограничивать возможность расширения кода, позволяя разработчикам удов! летворить любые свои потребности.

6.Tester должен уметь записывать нажатия клавиш в строки, совместимые с его модулем воспроизведения.

7.Сохраняемые сценарии Tester должны быть полными, т. е. готовыми к запуску.

8.Пользователь должен иметь возможность редактирования автоматически сге! нерированного сценария перед его сохранением.

9.Tester должен гарантировать правильность потока воспроизведения информа! ции, присваивая фокус конкретным окнам, в том числе любым дочерним эле! ментам управления.

Tester поддерживает практически полный набор функций, однако он, навер! ное, не решит всех задач, поставленных перед вашим отделом контроля качества, состоящим из 20 человек. Я просто хотел создать средство, которое позволило бы нам, программистам, автоматизировать блочное тестирование. Думаю, Tester от! вечает этим требованиям. Он очень помог мне при разработке WDBG, отладчика с графическим пользовательским интерфейсом (GUI), описанного в главе 4. Са! мый приятный аспект работы с Tester при создании WDBG заключался в том, что он избавил меня от многих тысяч нажатий клавиш. Как видите, я уже добрался до 16 главы и все еще могу двигать пальцами!

Использование Tester

Работать с Tester относительно просто. Сначала я расскажу про объект Tester и его использование в сценариях, а затем перейду к обсуждению записи сценариев при помощи программы TESTREC.EXE. Разобравшись с объектом, который Tester пре! доставляет вашим сценариям, вы сможете создавать их более эффективно.

Соседние файлы в предмете Программирование на C++