Тестирование Delphi GUI и модальные формы
В этом интересном посте в блоге о delphiXtreme я прочитал о встроенных возможностях тестирования графического интерфейса DUnit (в основном, альтернативный класс тестовых примеров). TGUITestCase
определяется в единицах GUITesting
это имеет несколько служебных функций для вызова действий в GUI). Я был вполне доволен этим, пока не заметил, что он не работает с модальными формами. Например, следующая последовательность не будет работать, если первая кнопка показывает форму модальной конфигурации:
Click ('OpenConfigButton');
Click ('OkButton');
Второй Click
выполняется только тогда, когда модальная форма закрыта, что я должен сделать вручную.
Я не знаю много о том, как модальные формы работают в фоновом режиме, но должен быть какой-то способ обойти это поведение. Наивно, я хочу как-то выполнить ShowModal
"в потоке", чтобы "основной поток" оставался отзывчивым. Теперь я знаю, что работает ShowModal
в теме наверно все испортит. Есть ли альтернативы? любой способ обойти блокирующий характер ShowModal
? У кого-нибудь есть опыт тестирования GUI в Delphi?
Я знаю о внешних инструментах (от QA или других), и мы используем эти инструменты, но этот вопрос касается тестирования GUI в IDE.
Спасибо!
2 ответа
Вы не можете проверить модальные формы, позвонив ShowModal
; потому что, как вы правильно поняли, это приводит к тому, что код вашего тестового примера "останавливается", пока модальная форма ожидает взаимодействия с пользователем.
Причина этого в том, что ShowModal
переключает вас во "вторичный цикл сообщений", который не завершается, пока форма не закроется.
Тем не менее, модальные формы все еще могут быть проверены.
- Показать обычно модальную форму с использованием нормального
Show
метод. - Это позволяет продолжить выполнение кода тестового примера и моделировать действия пользователя.
- Эти действия и эффекты могут быть проверены как обычно.
- Вам понадобится дополнительный тест, специфичный для модальных форм:
- Модальная форма обычно закрывается установкой модального результата.
- Тот факт, что вы использовали
Show
означает, что форма не будет закрыта при установке модального результата. - Что хорошо, потому что если вы теперь симулируете, нажимая кнопку "ОК"...
- Вы можете просто проверить, что
ModalResult
верно.
ПРЕДУПРЕЖДЕНИЕ
Вы можете использовать эту технику для тестирования конкретной модальной формы, явно показав ее немодально. Однако любой тестируемый код, который показывает модальную форму (например, Error Dialog), приостановит ваш тестовый случай.
Даже ваш пример кода: Click ('OpenConfigButton');
в результате вызывается ShowModal и не может быть протестирован таким образом.
Чтобы решить эту проблему, вам нужно, чтобы ваши "команды показа" были инъекционными в ваше приложение. Если вы не знакомы с внедрением зависимостей, я рекомендую видеоролики "Чистые разговоры по коду" Misko Hevery, доступные на You Tube. Затем во время тестирования вы вводите подходящую версию ваших "команд показа", которая не будет отображать модальную форму.
Например, ваша модальная форма может отображать диалоговое окно с ошибкой, если проверка не удалась при нажатии кнопки Ok.
Так:
1) Определите интерфейс (или абстрактный базовый класс) для отображения сообщений об ошибках.
IErrorMessage = interface
procedure ShowError(AMsg: String);
end;
2) Форма, которую вы тестируете, может содержать вставленную ссылку на интерфейс (FErrorMessage: IErrorMessage
) и использовать его для отображения ошибки при сбое проверки.
procedure TForm1.OnOkClick;
begin
if (Edit1.Text = '') then
FErrorMessage.ShowError('Please fill in your name');
else
ModalResult := mrOk; //which would close the form if shown modally
end;
3) Версия IErrorMessage по умолчанию, используемая / внедренная для производственного кода, будет просто отображать сообщение как обычно.
4) Тестовый код вводит фиктивную версию IErrorMessage, чтобы предотвратить приостановку ваших тестов.
5) Ваши тесты теперь могут выполнять случаи, которые обычно отображают сообщение об ошибке.
procedure TTestClass.TestValidationOfBlankEdit;
begin
Form1.Show; //non-modally
//Do not set a value for Edit1.Text;
Click('OkButton');
CheckEquals(0, Form1.ModalResult); //Note the form should NOT close if validation fails
end;
6) Вы можете сделать еще один шаг к ложному IErrorMessage, чтобы фактически проверить текст сообщения.
TMockErrorMessage = class(TInterfaceObject, IErrorMessage)
private
FLastErrorMsg: String;
protected
procedure ShowError(AMsg: String); //Implementaion trivial
public
property LastErrorMsg: String read FLastErrorMsg;
end;
TTestClass = class(TGUITesting)
private
//NOTE!
//On the test class you keep a reference to the object type - NOT the interface type
//This is so you can access the LastErrorMsg property
FMockErrorMessage: TMockErrorMessage;
...
end;
procedure TTestClass.SetUp;
begin
FMockErrorMessage := TMockErrorMessage.Create;
//You need to ensure that reference counting doesn't result in the
//object being destroyed before you're done using it from the
//object reference you're holding.
//There are a few techniques: My preference is to explicitly _AddRef
//immediately after construction, and _Release when I would
//otherwise have destroyed the object.
end;
7) Теперь более ранний тест становится:
procedure TTestClass.TestValidationOfBlankEdit;
begin
Form1.Show; //non-modally
//Do not set a value for Edit1.Text;
Click('OkButton');
CheckEquals(0, Form1.ModalResult); //Note the form should NOT close if validation fails
CheckEqulsString('Please fill in your name', FMockErrorMessage.LastErrorMsg);
end;
На самом деле в Delphi есть способ протестировать модальные окна. Когда отображается модальное окно, ваше приложение по-прежнему обрабатывает сообщения Windows, поэтому вы можете отправить сообщение в какое-нибудь вспомогательное окно непосредственно перед показом модального окна. Тогда ваше сообщение будет обработано из модального цикла, что позволит вам выполнить код, пока модальное окно все еще видно.
Недавно я работал над простой библиотекой, чтобы решить эту проблему. Вы можете скачать код здесь: https://github.com/tomazy/DelphiUtils (см.: FutureWindows.pas).
Пример использования:
uses
Forms,
FutureWindows;
procedure TFutureWindowsTestCase.TestSample;
begin
TFutureWindows.Expect(TForm.ClassName)
.ExecProc(
procedure (const AWindow: IWindow)
var
myForm: TForm;
begin
myForm := AWindow.AsControl as TForm;
CheckEquals('', myForm.Caption);
myForm.Caption := 'test caption';
myForm.Close();
end
);
with TForm.Create(Application) do
try
Caption := '';
ShowModal();
CheckEquals('test caption', Caption);
finally
Free;
end;
end;