2

我正在通过 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 不加载页面,测试返回 false。我不确定,但这可能是因为 TChromium 是异步初始化的,并且在执行测试时 TChromium 尚未完全初始化。

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

编辑 我已阅读此答案。就我而言,.Show 确实允许进入下一行测试,但似乎 TChromium 在那个阶段还没有完全初始化。我也尝试了tomazy的建议,但这也不起作用。

4

1 回答 1

3

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


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

在创建任何浏览器之前,GlobalCEFApp.GlobalContextInitialized 必须为 TRUE。如果它还没有初始化,我们稍后会使用一个简单的计时器来创建浏览器。

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

一种可行的方法是确保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...方法中添加超时参数,并在等待意外超时时让您的测试立即失败。

于 2018-01-14T13:55:32.620 回答