Тестирование Delphi GUI и модальные формы

В этом интересном посте в блоге о delphiXtreme я прочитал о встроенных возможностях тестирования графического интерфейса DUnit (в основном, альтернативный класс тестовых примеров). TGUITestCase определяется в единицах GUITesting это имеет несколько служебных функций для вызова действий в GUI). Я был вполне доволен этим, пока не заметил, что он не работает с модальными формами. Например, следующая последовательность не будет работать, если первая кнопка показывает форму модальной конфигурации:

Click ('OpenConfigButton');
Click ('OkButton');

Второй Click выполняется только тогда, когда модальная форма закрыта, что я должен сделать вручную.

Я не знаю много о том, как модальные формы работают в фоновом режиме, но должен быть какой-то способ обойти это поведение. Наивно, я хочу как-то выполнить ShowModal "в потоке", чтобы "основной поток" оставался отзывчивым. Теперь я знаю, что работает ShowModal в теме наверно все испортит. Есть ли альтернативы? любой способ обойти блокирующий характер ShowModal? У кого-нибудь есть опыт тестирования GUI в Delphi?

Я знаю о внешних инструментах (от QA или других), и мы используем эти инструменты, но этот вопрос касается тестирования GUI в IDE.

Спасибо!

2 ответа

Решение

Вы не можете проверить модальные формы, позвонив ShowModal; потому что, как вы правильно поняли, это приводит к тому, что код вашего тестового примера "останавливается", пока модальная форма ожидает взаимодействия с пользователем.

Причина этого в том, что ShowModal переключает вас во "вторичный цикл сообщений", который не завершается, пока форма не закроется.

Тем не менее, модальные формы все еще могут быть проверены.

  1. Показать обычно модальную форму с использованием нормального Show метод.
  2. Это позволяет продолжить выполнение кода тестового примера и моделировать действия пользователя.
  3. Эти действия и эффекты могут быть проверены как обычно.
  4. Вам понадобится дополнительный тест, специфичный для модальных форм:
    1. Модальная форма обычно закрывается установкой модального результата.
    2. Тот факт, что вы использовали Show означает, что форма не будет закрыта при установке модального результата.
    3. Что хорошо, потому что если вы теперь симулируете, нажимая кнопку "ОК"...
    4. Вы можете просто проверить, что 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;
Другие вопросы по тегам