13

在关于delphiXtreme的这篇有趣的博客文章中,我了解到 DUnit 的内置 GUI 测试功能(基本上是TGUITestCase在单元GUITesting中定义的替代测试用例类,它具有多个用于在 GUI 中调用操作的实用程序函数)。我对它非常满意,直到我注意到它不适用于模态表单。例如,如果第一个按钮显示模态配置表单,则以下序列将不起作用:

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

第二个Click仅在模态表单关闭时执行,我必须手动执行。

我不太了解模态表单在后台如何工作,但必须有某种方法来规避这种行为。天真地,我想以某种方式执行ShowModal“线程中”,以便“主线程”保持响应。现在我知道ShowModal在线程中运行可能会搞砸一切。有没有其他选择?有什么办法可以规避 a 的阻塞性质 ShowModal?有没有人在 Delphi 中进行 GUI 测试的经验?

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

谢谢!

4

2 回答 2

22

您不能通过调用来测试模态表单ShowModal;因为正如您正确地发现的那样,这会导致您的测试用例代码在模态表单等待用户交互时“暂停”。

这样做的原因是ShowModal将您切换到一个“辅助消息循环”,该循环在表单关闭之前不会退出。

但是,仍然可以测试模态形式。

  1. 使用normal方法显示通常的 Modal 形式。 Show
  2. 这允许您的测试用例代码继续,并模拟用户操作。
  3. 这些动作和效果可以正常测试。
  4. 您将需要一个对模态表单非常特殊的附加测试:
    1. 模态表单通常通过设置模态结果来关闭。
    2. 您使用的事实Show意味着不会通过设置模态结果来关闭表单。
    3. 这很好,因为如果您现在模拟单击“确定”按钮...
    4. 您可以简单地检查ModalResult是否正确。

警告

您可以使用此技术通过以非模态方式显式显示特定的模态表单来测试它。但是,任何显示模式形式的测试代码(例如错误对话框)都会暂停您的测试用例。

即使是您的示例代码:也会Click ('OpenConfigButton');导致 ShowModal 被调用,并且无法以这种方式进行测试。

要解决此问题,您需要将“显示命令”注入到您的应用程序中。如果您不熟悉依赖注入,我推荐您在 YouTube 上观看 Misko Hevery 的 Clean Code Talks 视频。然后在测试时,你注入一个合适的“显示命令”版本,它不会显示模式形式。

例如,如果单击 Ok 按钮时验证失败,您的模态表单可能会显示错误对话框。

所以:

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;
于 2011-07-04T16:37:32.700 回答
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;
于 2011-08-11T10:25:29.960 回答