CEF4Delphi 和 DUnit

CEF4Delphi and DUnit

我正在通过 DUnit 在我的应用程序中测试我使用 CEF4Delphi 创建的几个进程。

以下是重现问题的 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 不会加载页面并且测试 returns false。 我不确定,但这可能是因为 TChromium 是异步初始化的,当执行测试时,TChromium 还没有完全初始化。

在这种情况下如何执行我的测试?

编辑 我读过 this answer 。在我的例子中,.Show 确实允许进行到测试的下一行,但似乎 TChromium 在那个阶段还没有完全初始化。我也尝试了 tomazy 的建议,但这也不起作用。

以目前的形式,您的测试不可能通过。 Chromium 的加载是延迟的,只会在未来的某个时间加载。但是您的测试会立即检查它是否已加载。测试异步代码是可能的,但它确实会使您的测试变得一团糟。我提醒您要小心您正在测试的内容。您可能想使用其他工具(如 Selenium)进行页面行为测试,并将您的 Delphi 测试重点放在您是否在需要的情况下加载了正确的页面。


粗略查看 CEF4 演示代码可以揭示创建可能延迟的原因。

GlobalCEFApp.GlobalContextInitialized has to be TRUE before creating any browser. If it's not initialized yet, we use a simple timer to create the browser later.

警告:全局状态会对单元测试造成严重破坏。您需要进一步调查以确定如何最好地确保您的测试不受此状态的负面影响。

一种可能有效的方法是确保在开始 运行 任何测试之前初始化 GlobalCEFApp.GlobalContextInitialized。但我怀疑这将是一个相当有限的解决方案,因为虽然我不熟悉 TChromiumWindow 组件,但我怀疑它的许多交互是异步的。你可以触发一些东西,但是你必须等待事件回调来确定最终结果。

这是您的测试代码将变得混乱的地方。例如,假设您的表单旨在在 Chromium window 完全初始化后立即自动加载特定页面。您的测试必须执行以下操作:

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... 方法添加一个超时参数,并使您的测试在以下情况下立即失败等待意外超时。