CEF4Delphi и DUnit

Я тестирую несколько процессов, которые я создал с помощью CEF4Delphi в моем приложении через DUnit.

Ниже приводится MCVE для воспроизведения проблемы:

unit MyUnit;

interface

{$I cef.inc}

uses
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Variants,
  System.Classes,
  Vcl.Graphics,
  Vcl.Controls,
  Vcl.Forms,
  Vcl.Dialogs,
  uCEFWindowParent,
  uCEFChromiumWindow,
  uCEFChromium,
  Vcl.ExtCtrls,
  Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    ChromiumWindow1: TChromiumWindow;
    Timer1: TTimer;
    procedure Timer1Timer(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure ChromiumWindow1AfterCreated(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    FChromiumCreated: Boolean;
    procedure WMMove(var aMessage: TWMMove); message WM_MOVE;
    procedure WMMoving(var aMessage: TMessage); message WM_MOVING;
  public
    { Public declarations }
    function IsChromiumCreated: Boolean;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ChromiumWindow1AfterCreated(Sender: TObject);
begin
  ChromiumWindow1.LoadURL('https://www.google.com');
  FChromiumCreated := True;

end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FChromiumCreated := False;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  if not (ChromiumWindow1.CreateBrowser) then
    Timer1.Enabled := True;
end;

function TForm1.IsChromiumCreated: Boolean;
begin
  Result := FChromiumCreated;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  if not (ChromiumWindow1.CreateBrowser) and not (ChromiumWindow1.Initialized) then
    Timer1.Enabled := True
end;

procedure TForm1.WMMove(var aMessage: TWMMove);
begin
  inherited;
  if (ChromiumWindow1 <> nil) then
    ChromiumWindow1.NotifyMoveOrResizeStarted;
end;

procedure TForm1.WMMoving(var aMessage: TMessage);
begin
  inherited;
  if (ChromiumWindow1 <> nil) then
    ChromiumWindow1.NotifyMoveOrResizeStarted;
end;

end.

и следующий тестовый пример:

unit TestMyTest;
{

  Delphi DUnit Test Case
  ----------------------
  This unit contains a skeleton test case class generated by the Test Case Wizard.
  Modify the generated code to correctly setup and call the methods from the unit
  being tested.

}

interface

uses
  TestFramework,
  Vcl.Forms,
  MyUnit,
  System.Classes;

type
  // Test methods for class TForm1

  TestTForm1 = class(TTestCase)
  strict private
    FFormHolder: TForm;
    FForm1: TForm1;
  public
    procedure SetUp; override;
    procedure TearDown; override;
  published
    procedure TestFormActivate;
  end;

implementation

procedure TestTForm1.SetUp;
begin
  Application.Initialize;
  FForm1 := TForm1.Create(nil);
  Application.Run;
end;

procedure TestTForm1.TearDown;
begin
  FForm1.Free;
  FForm1 := nil;
end;

procedure TestTForm1.TestFormActivate;
begin
  FForm1.Show;
  CheckTrue(FForm1.IsChromiumCreated);
end;

initialization
  // Register any test cases with the test runner
  RegisterTest(TestTForm1.Suite);

end.

Если я использую.Show, инструкция FChromiumCreated := True; не выполняется, TChromium не загружает страницу и тест возвращает false. Я не уверен, но это может быть из-за того, что TChromium инициализируется асинхронно и когда тест выполняется, TChromium еще не инициализирован полностью.

Как можно выполнить мой тест в этом случае?

Изменить Я прочитал этот ответ. В моем случае.Show позволяет перейти к следующей строке теста, но кажется, что TChromium не инициализировался полностью на этом этапе. Я также попробовал предложение от Томази, но это тоже не работает.

1 ответ

Решение

Нет шансов, что ваш тест пройдёт в его текущей форме. Загрузка Chromium задерживается и будет загружена только в какой-то момент в будущем. Тем не менее, ваш тест немедленно проверяет, загружен ли он. Тестирование асинхронного кода возможно, но это действительно запутывает ваши тесты. Я бы предупредил вас быть осторожным с тем, что вы тестируете. Возможно, вы захотите использовать другой инструмент, такой как Selenium, для тестов поведения страниц и сфокусировать свои тесты Delphi на том, загружаете ли вы нужные страницы в требуемых ситуациях.


Беглый взгляд на демонстрационный код CEF4 показывает, что создание может быть отложено.

GlobalCEFApp.GlobalContextInitialized должно быть ИСТИНА перед созданием любого браузера. Если он еще не инициализирован, мы используем простой таймер для создания браузера позже.

ВНИМАНИЕ: Глобальное состояние может нанести ущерб модульному тестированию. Вам нужно будет продолжить расследование, чтобы определить, как лучше всего убедиться, что это состояние не оказывает негативного влияния на ваши тесты.

Один из подходов, который может сработать, заключается в обеспечении GlobalCEFApp.GlobalContextInitialized инициализируется перед началом любых тестов. Но я подозреваю, что это будет довольно ограниченное решение, потому что, хотя я не знаком с TChromiumWindow компонент, я подозреваю, что многие из его взаимодействий являются асинхронными. Вы можете что-то вызвать, но тогда вам придется ждать обратного вызова события, чтобы определить окончательный результат.

Это где ваш тестовый код станет грязным. Например, предположим, что ваша форма предназначена для автоматической загрузки определенной страницы, как только окно Chromium будет полностью инициализировано. Ваш тест должен будет сделать что-то следующее:

procedure TestTForm1.TestBrowserLoad;
begin
  FForm1.InitialPage := 'https://google.com';
  FForm1.Show;
  WaitForChromiumCreated(Form1.ChromiumWindow1); { <-- This is the tricky bit }
  CheckTrue(FForm1.IsChromiumCreated);
end;

по существу WaitForChromiumCreated необходимо разрешить цикл сообщений вашей основной формы, чтобы продолжить отправку сообщений. Но также блокируйте обработку в вашем методе тестирования. Также необходимо достоверно знать, когда компонент полностью инициализирован. Это ситуация, когда ProcessMessages() оправдано, потому что вы не в состоянии перестроить CEF4.

Что-то в следующих направлениях должно сработать.

procedure WaitForChromiumCreated(AChromiumWindow: TChromiumWindow);
begin
  while True do
  begin
    if (AChromiumWindow.Initialized) then Break;
    { You'll also need a way to break out of this loop
      if something goes wrong and the component cannot 
      initialise, or if the tests are aborted. }
    Application.ProcessMessages();
  end;
end;

СОВЕТ: Я также настоятельно рекомендую добавить параметр времени ожидания для всех Wait... методы и заставить ваши тесты сразу же провалиться, если время ожидания неожиданно истекло.

Другие вопросы по тегам