delphi - CEF4Delphi 和 DUnit

标签 delphi chromium-embedded dunit cef4delphi

我正在测试用 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 尚未完全初始化。

在这种情况下如何进行测试?

编辑 我已阅读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 窗口完全初始化后自动加载特定页面。您的测试必须执行以下操作:

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

关于delphi - CEF4Delphi 和 DUnit,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48248267/

相关文章:

c++ - CEF base::ThreadRestrictions::AssertIOAllowed() 断言在应用程序退出时失败

delphi - 测量 Delphi 中的代码覆盖率

linux - 如何在Delphi中实时读取cygwin程序的命令行输出?

delphi - TIdHTTP - Delphi XE 下 session 已过期消息

android - 有什么方法可以在 Android 应用程序中嵌入浏览器?

c++ - 无法在 debian 7 64 位上链接 chromium 嵌入式框架 3

delphi - DUnit: 'Global' 设置和拆卸

delphi - hudson 的 dunit 测试结果消息

Windows 7 x64 上的 Delphi 问题?

Delphi:能否在 DLL 中定义组件类并在运行时加载并创建它?