如何通过 UI 在测试代码单元中创建新客户?

How do I create a new customer in a test codeunit through UI?

我想模拟创建客户、项目或发票之类的东西,尽管是通过使用测试页面。我的第一个想法是使用 OpenNew()-函数:

[Test]
[HandlerFunctions('ModalPageHandler,ExpectedConfirmHandler')]
procedure SampleTest()
// [FEATURE] Customer
// [SCENARIO] Create a new Customer
// [PREREQ] super rights not necessary
var
  tpCustomerCard: TestPage "Customer Card";
  cNewCustomerNo: Code[20];
  rCustomer: Record Customer;
begin
  Initialize();

  // [GIVEN] Office 365 Business Full permissions
  gAny.SetSeed(5);
  gLibraryLowerPermissions.SetO365BusFull();

  // [WHEN] Creating a new user
  tpCustomerCard.OpenNew();
  tpCustomerCard.Name.SetValue(gAny.AlphabeticText(gAny.IntegerInRange(20)));
  cNewCustomerNo := tpCustomerCard."No.".Value;
  gLibraryVariableStorage.Enqueue('');
  gLibraryVariableStorage.Enqueue(true);
  tpCustomerCard.Close();

  // [THEN] create user
  gAssert.IsFalse(cNewCustomerNo = '', 'New Customer No. created');
  gAssert.IsTrue(rCustomer.Get(cNewCustomerNo), 'New Customer created');
end;

[ModalPageHandler]
procedure ModalPageHandler(var tpCustomerCard: TestPage "Customer Card");
begin
end;

[ConfirmHandler]
procedure ExpectedConfirmHandler(pQuestion: Text[1024]; VAR pvReply: Boolean)
// Call the following in the Test function
//gLibraryVariableStorage.Enqueue('ExpectedConfirmText');
//gLibraryVariableStorage.Enqueue(true); // or false, depending of the reply you want if below question is asked. Any other question will throw an error
begin
  gAssert.ExpectedMessage(gLibraryVariableStorage.DequeueText(), pQuestion);
  pvReply := gLibraryVariableStorage.DequeueBoolean();
end;

然而,这总是return以下错误:

Unexpected CLR exception thrown.: Microsoft.Dynamics.Framework.UI.FormAbortException: Page New - Customer Card has to close. ---> Microsoft.Dynamics.Nav.Types.Exceptions.NavNCLMissingUIHandlerException: Unhandled UI: ModalPage 1340 ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> Microsoft.Dynamics.Nav.Types.Exceptions.NavNCLMissingUIHandlerException: Unhandled UI: ModalPage 1340\   at Microsoft.Dynamics.Nav.Runtime.NavTestExecution.FindHandler(NavHandlerType handlerType, NavApplicationObjectBase appObject, Boolean throwIfNotFound, String handlerDescription)\   at Microsoft.Dynamics.Nav.Runtime.NavTestExecution.TestHandleModalForm(NavForm form, FormResult& action, NavFormRuntimeParameters parameters)\   at Microsoft.Dynamics.Nav.Runtime.NavForm.RunModal(NavRecord record, Int32 fieldNo)\   at Microsoft.Dynamics.Nav.Runtime.NavFormHandle.RunModal(NavRecord record, Int32 fieldNo)\   at Microsoft.Dynamics.Nav.BusinessApplication.Record1300.NewCustomerFromTemplate_Scope__1848449490.OnRun()\   at Microsoft.Dynamics.Nav.Runtime.NavMethodScope.Run()\   at Microsoft.Dynamics.Nav.BusinessApplication.Record1300.NewCustomerFromTemplate(INavRecordHandle customer)\   at Microsoft.Dynamics.Nav.BusinessApplication.Record1300.OnInvoke(Int32 memberId, Object[] args)\   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit1389.CreateCustomerFromConfigTemplate_Scope_587295807.OnRun()\   at Microsoft.Dynamics.Nav.Runtime.NavMethodScope.Run()\   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit1389.CreateCustomerFromConfigTemplate(INavRecordHandle customer, ByRef`1 isHandled)\   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit1389.OnInsertCustomerFromTemplateHandler_Scope.OnRun()\   at Microsoft.Dynamics.Nav.Runtime.NavMethodScope.Run()\   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit1389.OnInsertCustomerFromTemplateHandler(INavRecordHandle customer, ByRef`1 result, ByRef`1 isHandled)\   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit1389.OnInvoke(Int32 memberId, Object[] args)\   at Microsoft.Dynamics.Nav.EventSubscription.NavEventScope.CallEventSubscriberInternal(NavEventSubscription subscriber, NavApplicationObjectBase subscriberInstance, Object[] parameters)\   at Microsoft.Dynamics.Nav.EventSubscription.NavEventScope.CallEventSubscriber(NavApplicationObjectBase callingApplicationObject, NavEventSubscription subscriber, NavApplicationObjectBase subscriberInstance, Object[] parameters)\   at Microsoft.Dynamics.Nav.EventSubscription.NavEventScope.ProcessCallToTypeAndManualSubscriptions(NavApplicationObjectBase callerApplicationObject, NavEventSubscription[] subscriptions, PrepareParametersCallBack prepareParameters)\   at Microsoft.Dynamics.Nav.Runtime.NavMethodScope.RunEvent()\   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit1381.OnInsertCustomerFromTemplate(INavRecordHandle customer, ByRef`1 result, ByRef`1 isHandled)\   at Microsoft.Dynamics.Nav.Runtime.NavMethodScope.Run()\   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit1381.InsertCustomerFromTemplate(INavRecordHandle customer)\   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit1381.OnInvoke(Int32 memberId, Object[] args)\   at Microsoft.Dynamics.Nav.BusinessApplication.Page21.CreateCustomerFromTemplate_Scope_857168676.OnRun()\   at Microsoft.Dynamics.Nav.Runtime.NavMethodScope.Run()\   at Microsoft.Dynamics.Nav.BusinessApplication.Page21.CreateCustomerFromTemplate()\   at Microsoft.Dynamics.Nav.BusinessApplication.Page21.OnAfterGetCurrRecord_Scope.OnRun()\   at Microsoft.Dynamics.Nav.Runtime.NavMethodScope.Run()\   at Microsoft.Dynamics.Nav.BusinessApplication.Page21.OnAfterGetCurrRecord()\   at Microsoft.Dynamics.Nav.Runtime.NavForm.RaiseOnAfterGetCurrRecord()\   at Microsoft.Dynamics.Nav.Runtime.NavForm.AfterGetCurrRecord()\   --- End of inner exception stack trace ---\   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)\   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)\   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)\   at Microsoft.Dynamics.Nav.Runtime.NavApplicationMethod.InvokeMethod(ITreeObject obj, String methodName, Object[] args, Boolean resolveHandler, Boolean throwOnNotFound)\   --- End of inner exception stack trace ---\   at Microsoft.Dynamics.Nav.Runtime.NavApplicationMethod.InvokeMethod(ITreeObject obj, String methodName, Object[] args, Boolean resolveHandler, Boolean throwOnNotFound)\   at Microsoft.Dynamics.Nav.Service.NSFormApplicationCode.<>c__DisplayClass7_0.<Invoke>b__0(NsDataAccess dataAccess)\   at Microsoft.Dynamics.Nav.Service.NsFormDataAccess.RunWithFormDataAccess[T](ITreeObject parent, NavForm form, Func`2 func)\   at Microsoft.Dynamics.Nav.Service.NsDataAccess.RunWithDataAccess[T](ITreeObject parent, NavRecordState state, Boolean instantiateNewForm, Guid parentFormHandle, String parentControlName, Boolean& instantiatedForm, Func`2 func)\   at Microsoft.Dynamics.Nav.Service.NsDataAccess.RunWithDataAccess[T](ITreeObject parent, NavRecordState state, Func`2 func)\   at INavServiceWrapper.InvokeApplicationMethod(ApplicationMethodRequest , NavRecordState )\   at Microsoft.Dynamics.Nav.Types.AsyncNavServiceWrapper.<>c__DisplayClass119_0.<BeginInvokeApplicationMethod>b__0()\   at Microsoft.Dynamics.Nav.Types.AsyncNavServiceWrapper.Invoke(Func`1 action, AsyncCallback callback, Object asyncState, Object extraState)\   at Microsoft.Dynamics.Nav.Client.ServiceConnectionBase.<>c__DisplayClass21_0.<InvokeApplicationMethod>b__0(IAsyncNavService server)\   at Microsoft.Dynamics.Nav.Runtime.TestServiceConnection.CallServer[T](BeginCallServerMethod beginCallServerMethod, EndCallServerMethod`1 endCallServerMethod)\   at Microsoft.Dynamics.Nav.Client.ServiceConnectionBase.InvokeApplicationMethod(ApplicationCodeType objectType, Int32 objectId, String methodName, NavDataSet& dataSet, NavRecordState& state, Object[]& args)\   at Microsoft.Dynamics.Nav.Client.DataBinder.NstDataAccess.InvokeOnAfterGetCurrentRecord(NavDataSet& navDataSet, NavRecordState& navRecordState)\   at Microsoft.Dynamics.Nav.Client.DataBinder.NstDataAccess.InvokeOnNewRecord(NavRecord record, NavRecord xrec, NavRecordState state, Object[] args)\   at Microsoft.Dynamics.Nav.Client.DataBinder.NstDataAccess.OnNewRecord(Boolean clearFormState, NavRecord xrec, Object[] args)\   --- End of inner exception stack trace ---\   at Microsoft.Dynamics.Nav.Client.DataBinder.NstDataAccess.Abort(NavBaseException exception)\   at Microsoft.Dynamics.Nav.Client.DataBinder.NstDataAccess.OnNewRecord(Boolean clearFormState, NavRecord xrec, Object[] args)\   at Microsoft.Dynamics.Nav.Client.DataBinder.NavBindingManager.EnsureOnNewRecordIsUpToDate()\   at Microsoft.Dynamics.Nav.Client.DataBinder.NavBindingManager.OnCurrentRowChanged(CurrentRowChangeEventArgs e)\   at Microsoft.Dynamics.Framework.UI.BindingManager.set_CurrentCollectionIndex(Int32 value)\   at Microsoft.Dynamics.Framework.UI.VirtualRows.UpdateAfterInsert(Int32 index, Boolean needsUpdateCurrentRow)\   at Microsoft.Dynamics.Framework.UI.VirtualRows.AddWithoutValidation(RowEntry row, Boolean needsUpdateCall, Boolean needsUpdateCurrentRow, Boolean insertFirst, Boolean raiseEvents)\   at Microsoft.Dynamics.Framework.UI.VirtualRows.Insert()\   at Microsoft.Dynamics.Nav.Client.TestPageClient.TestPageClientSession.CreateLogicalForm(Int32 formId, FormState formState)\   at Microsoft.Dynamics.Nav.Client.TestPageClient.TestPageClientSession.CreatePage(Int32 id, ViewMode mode)\   at Microsoft.Dynamics.Nav.Runtime.NavTestPage.Open(ViewMode mode)\   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit50100.SampleTest_Scope__1526614456.OnRun()\   at Microsoft.Dynamics.Nav.Runtime.NavMethodScope.Run()\   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit50100.SampleTest()

调试时,代码自动在Mini Customer Template.dal的这个函数处断,即使没有断点:

[Obsolete('Will be removed with other functionality related to "old" templates. Replaced by new templates.', '18.0')]
    procedure NewCustomerFromTemplate(var Customer: Record Customer): Boolean
    var
        ConfigTemplateHeader: Record "Config. Template Header";
        ConfigTemplates: Page "Config Templates";
    begin
        ConfigTemplateHeader.SetRange("Table ID", DATABASE::Customer);
        ConfigTemplateHeader.SetRange(Enabled, true);

        if ConfigTemplateHeader.Count = 1 then begin
            ConfigTemplateHeader.FindFirst;
            InsertCustomerFromTemplate(ConfigTemplateHeader, Customer);
            exit(true);
        end;

        if (ConfigTemplateHeader.Count > 1) and GuiAllowed then begin
            ConfigTemplates.SetTableView(ConfigTemplateHeader);
            ConfigTemplates.LookupMode(true);
            ConfigTemplates.SetNewMode;
            if ConfigTemplates.RunModal = ACTION::LookupOK then begin
                ConfigTemplates.GetRecord(ConfigTemplateHeader);
                InsertCustomerFromTemplate(ConfigTemplateHeader, Customer);
                exit(true);
            end;
        end;

        exit(false);
    end;

具体来说,它总是在最后 if 处停止,很可能是因为 ConfigTemplates 未定义的 。这可能是因为在正常创建新客户或项目时,首先打开页面 Config Templates 让用户选择模板,并且由于我的代码单元跳过了这一步,所以其中一个变量最终未定义。然后我尝试通过从 Customer List 页面调用 New Customer 操作来打开 Customer Card 页面,但由于这不是自定义操作,我无法访问它。

为了尽可能地复制用户交互,我更愿意通过 UI 中的操作打开 windows,但如果 AL 无法做到这一点,我至少想要前者工作的方法。如何使用模板打开卡片页面而不立即测试失败?

如果您查看错误消息,您会发现它抱怨 ModalPage 1340 缺少 ModalPageHandler,即 Config Templates 页面。

您需要为该特定页面定义 ModalPageHandler

[ModalPageHandler]
procedure ConfigTemplatesHandler(var ConfigTemplates: TestPage "Config Templates")
begin
    // This will select the first template available        
    ConfigTemplates.OK().Invoke(); 
end;

然后您需要将其添加为 HandlerFunction 用于您的测试程序:

[Test]
[HandlerFunctions('ConfigTemplatesHandler,ExpectedConfirmHandler')]
procedure SampleTest()
begin
  ...
end;