Delphi GUI测试和模态窗口

13

我在delphiXtreme的这篇有趣的博客文章中读到了关于DUnit内置GUI测试功能的介绍(基本上是另一种测试用例类TGUITestCase,定义在单元GUITesting中,并具有多个实用程序函数以在GUI中调用操作)。我对此感到相当满意,直到我发现它无法与模态窗体一起使用。例如,如果第一个按钮显示模态配置表单,则以下序列将无法正常工作:

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

当模态窗口关闭时,第二个Click才会被执行,需要手动进行操作。

我对模态窗口的工作原理不是很了解,但肯定有一些方法可以避免这种行为。天真地说,我想以某种方式在“线程”中执行ShowModal,以使“主线程”保持响应性。现在我知道在线程中运行ShowModal可能会搞砸一切。是否有任何替代方案?如何规避 ShowModal的阻塞性?在 Delphi 中有没有 GUI 测试经验的人?

我知道外部工具(来自 QA 或其他来源),我们使用这些工具,但这个问题是关于在 IDE 中进行 GUI 测试的。

谢谢!


你不能希望在不同的线程中运行 ShowModal 代码。那是不可行的。你需要做的是在调用 ShowModal 之前向你的窗体发送一条消息。当窗体处理该消息时,它可以调用测试代码,进而点击按钮等操作。 - David Heffernan
OpenCTF也可以进行GUI测试。 - menjaraz
2个回答

22
您不能通过调用ShowModal来测试模态表单,因为正如您已经正确发现的那样,这会导致您的测试案例代码“暂停”,而模态表单等待用户交互。
原因是ShowModal会将您切换到一个“辅助消息循环”,该消息循环直到表单关闭才退出。
然而,模态表单仍然可以被测试。
1. 使用普通的Show方法显示通常是模态的表单。 2. 这允许您的测试案例代码继续运行,并模拟用户操作。 3. 可以像平常一样测试这些操作和效果。 4. 您还需要一个特别针对模态表单的额外测试:
a. 模态表单通常是通过设置模态结果来关闭的。
b. 使用Show意味着不会通过设置模态结果来关闭表单。
c. 如果现在模拟单击“确定”按钮...
d. 您只需检查ModalResult是否正确即可。
警告:
您可以使用此技术通过显式非模态方式显示特定的模态表单进行测试。但是,任何正在测试中的代码如果显示模态表单(例如错误对话框),都将暂停您的测试案例。
即使您的示例代码:Click ('OpenConfigButton');也会导致调用ShowModal,并且不能以这种方式进行测试。
为了解决这个问题,您需要使“显示命令”可注入到您的应用程序中。如果您不熟悉依赖项注入,我建议您在YouTube上查看Misko Hevery的《干净代码演讲》视频。然后在测试期间,您注入一个适当的版本的“显示命令”,该版本不会显示模态表单。
例如,如果单击“确定”按钮时验证失败,则模态表单可能会显示错误对话框。
因此:
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) 您可以进一步使用模拟的 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;

+1 感谢你的出色回答及付出的努力。那个 +10 的按钮在哪里呢? ;) - jpfollenius

11

实际上,Delphi 中有一种测试模式窗口的方法。当模态窗口显示时,你的应用程序仍然会处理窗口消息,因此你可以在显示模态窗口之前向某个助手窗口发送一条消息。然后,在模态循环中会处理你的消息,这样你就可以在模态窗口仍然可见的情况下执行代码。

最近我一直在开发一个简单的库来解决这个问题。你可以从这里下载代码: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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接