Подтвердить что ты не робот

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

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

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

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

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

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

Спасибо!

4b9b3361

Ответ 1

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

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

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

  • Покажите обычно модальную форму, используя метод обычный Show.
  • Это позволяет продолжить выполнение кода тестового кода и имитировать действия пользователя.
  • Эти действия и эффекты можно протестировать как обычно.
  • Вам понадобится дополнительный тест, весьма специфичный для модальных форм:
    • Модальная форма обычно закрывается установкой модального результата.
    • Тот факт, что вы использовали Show, означает, что форма не будет закрыта, установив модальный результат.
    • Что хорошо, потому что если вы теперь имитируете нажатие кнопки "Ok"...
    • Вы можете просто проверить правильность ModalResult.

ПРЕДУПРЕЖДЕНИЕ

Вы можете использовать эту технику для проверки конкретной модальной формы, явно показывая ее немодально. Тем не менее, любой проверенный код, который показывает модальную форму (например, Диалог ошибок), приостанавливает ваш тестовый пример.

Даже ваш пример кода: Click ('OpenConfigButton'); приводит к вызову ShowModal и не может быть протестирован таким образом.

Чтобы решить эту проблему, вам нужно, чтобы ваши команды show были введены в ваше приложение. Если вы не знакомы с инъекцией зависимостей, я рекомендую видеоролики Misko Hevery Clean Code Talks, доступные на 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) Вы можете сделать mock 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;

Ответ 2

На самом деле есть способ проверить модальные окна в 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;