mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 14:46:55 +01:00
Refine self-improvement review guidance
Narrow IsHandled, label-scope, UI-handler, checkpoint, and bulk-operation guidance to evidence-backed false-positive boundaries. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
841b4e7cab
commit
5f1cff2fb6
21 changed files with 99 additions and 155 deletions
|
|
@ -1,43 +1,28 @@
|
|||
codeunit 50401 "Test UI Handlers Bad"
|
||||
codeunit 50401 "Test UI Handler Proof Bad"
|
||||
{
|
||||
Subtype = Test;
|
||||
|
||||
// Several wiring mistakes, each of which fails at runtime rather than as a
|
||||
// clean assertion the reviewer can read:
|
||||
// * A UI call with no listed handler -> "unhandled UI" abort (the Message
|
||||
// below has no handler).
|
||||
// * The mirror mistake, listing a handler the path never hits, instead
|
||||
// fails with "handler function was not executed".
|
||||
// * A handler that hardcodes its answer and asserts inline, with no
|
||||
// enqueue/dequeue -> nothing proves the RIGHT dialog fired the RIGHT
|
||||
// number of times, and a failed inline assert can be swallowed by the
|
||||
// calling UI operation.
|
||||
[Test]
|
||||
[HandlerFunctions('ConfirmHandler')]
|
||||
procedure PostDocumentConfirmsAndMessages()
|
||||
[HandlerFunctions('CustomerCardHandler')]
|
||||
procedure CustomerCardActionSucceeds()
|
||||
var
|
||||
Customer: Record Customer;
|
||||
begin
|
||||
// No Initialize(): a value leaked by an earlier test corrupts this one.
|
||||
RunPostingThatConfirmsAndMessages();
|
||||
// No AssertEmpty(): a missing or extra dialog goes unnoticed.
|
||||
Customer.Get('10000');
|
||||
ActionSucceeded := true;
|
||||
|
||||
Page.RunModal(Page::"Customer Card", Customer);
|
||||
|
||||
// This only proves a value assigned before the action stayed true.
|
||||
Assert.IsTrue(ActionSucceeded, 'The customer card action failed.');
|
||||
end;
|
||||
|
||||
local procedure RunPostingThatConfirmsAndMessages()
|
||||
[ModalPageHandler]
|
||||
procedure CustomerCardHandler(var CustomerCard: TestPage "Customer Card")
|
||||
begin
|
||||
// Raises a Confirm AND a Message, but only ConfirmHandler is listed:
|
||||
// the Message has nothing to intercept it -> unhandled-UI runtime abort.
|
||||
if Confirm('Post this document?', false) then
|
||||
Message('Posting completed.');
|
||||
end;
|
||||
|
||||
[ConfirmHandler]
|
||||
procedure ConfirmHandler(Question: Text[1024]; var Reply: Boolean)
|
||||
begin
|
||||
// Hardcoded expectation and hardcoded reply. If the wrong dialog fires,
|
||||
// this inline assert may never surface as the test's verdict.
|
||||
Assert.AreEqual('Post this document?', Question, 'Wrong confirm.');
|
||||
Reply := true;
|
||||
end;
|
||||
|
||||
var
|
||||
Assert: Codeunit "Library Assert";
|
||||
ActionSucceeded: Boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +1,28 @@
|
|||
codeunit 50400 "Test UI Handlers Good"
|
||||
codeunit 50400 "Test UI Handler Capture Good"
|
||||
{
|
||||
Subtype = Test;
|
||||
|
||||
[Test]
|
||||
[HandlerFunctions('ConfirmHandler,PostMessageHandler')]
|
||||
procedure PostDocumentConfirmsAndMessages()
|
||||
[HandlerFunctions('CustomerCardHandler')]
|
||||
procedure CustomerCardShowsSelectedCustomer()
|
||||
var
|
||||
Customer: Record Customer;
|
||||
begin
|
||||
Initialize();
|
||||
Customer.Get('10000');
|
||||
CapturedCustomerNo := '';
|
||||
|
||||
// [GIVEN] the test enqueues, in interaction order, what each handler
|
||||
// will see and how it should answer: the Confirm's expected
|
||||
// question plus the reply to return, then the expected Message.
|
||||
LibraryVariableStorage.Enqueue('Post this document?'); // expected question (substring)
|
||||
LibraryVariableStorage.Enqueue(true); // reply ConfirmHandler returns
|
||||
LibraryVariableStorage.Enqueue('Posting completed.'); // expected message (substring)
|
||||
Page.RunModal(Page::"Customer Card", Customer);
|
||||
|
||||
// [WHEN] the code under test raises the Confirm and then the Message
|
||||
RunPostingThatConfirmsAndMessages();
|
||||
|
||||
// [THEN] every enqueued expectation was consumed exactly once
|
||||
LibraryVariableStorage.AssertEmpty();
|
||||
Assert.AreEqual(Customer."No.", CapturedCustomerNo, 'The customer card opened for the wrong customer.');
|
||||
end;
|
||||
|
||||
local procedure Initialize()
|
||||
[ModalPageHandler]
|
||||
procedure CustomerCardHandler(var CustomerCard: TestPage "Customer Card")
|
||||
begin
|
||||
// Clear leftover values so a value leaked by an earlier test cannot
|
||||
// cascade into this one.
|
||||
LibraryVariableStorage.Clear();
|
||||
end;
|
||||
|
||||
local procedure RunPostingThatConfirmsAndMessages()
|
||||
begin
|
||||
// Stands in for the production routine that confirms, then messages.
|
||||
if Confirm('Post this document?', false) then
|
||||
Message('Posting completed.');
|
||||
end;
|
||||
|
||||
[ConfirmHandler]
|
||||
procedure ConfirmHandler(Question: Text[1024]; var Reply: Boolean)
|
||||
begin
|
||||
// Verify the RIGHT dialog fired (substring match), then return the
|
||||
// reply the test enqueued for it.
|
||||
Assert.ExpectedConfirm(LibraryVariableStorage.DequeueText(), Question);
|
||||
Reply := LibraryVariableStorage.DequeueBoolean();
|
||||
end;
|
||||
|
||||
[MessageHandler]
|
||||
procedure PostMessageHandler(Message: Text[1024])
|
||||
begin
|
||||
Assert.ExpectedMessage(LibraryVariableStorage.DequeueText(), Message);
|
||||
CapturedCustomerNo := CustomerCard."No.".Value();
|
||||
end;
|
||||
|
||||
var
|
||||
Assert: Codeunit "Library Assert";
|
||||
LibraryVariableStorage: Codeunit "Library - Variable Storage";
|
||||
CapturedCustomerNo: Code[20];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: testing
|
||||
keywords: [handler, handlerfunctions, confirm, message, strmenu, variable-storage, enqueue, unhandled-ui]
|
||||
keywords: [handler, handlerfunctions, confirm, message, strmenu, variable-storage, enqueue, capture, runmodal, unhandled-ui]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Wire and verify UI handlers with enqueue-driven expectations
|
||||
# Wire UI handlers and verify meaningful outcomes
|
||||
|
||||
## Description
|
||||
|
||||
A test runs headless: there is no interactive user to answer a dialog. Every UI call the executed path raises — `Confirm`, `Message`, error dialogs, `Page.Run`/`RunModal`, `Report.Run`/`RunModal`, request pages, `StrMenu`, `Notification.Send` — must be intercepted by a handler carrying the matching attribute (`[ConfirmHandler]`, `[MessageHandler]`, `[StrMenuHandler]`, `[ModalPageHandler]`, …) and named in the method's `[HandlerFunctions(...)]`. The list is a two-sided contract: raise a UI call with no listed handler and the platform aborts with an *unhandled UI* error; list a handler the path never hits and it fails with *"handler function was not executed"*. Both are runtime failures — the test never reaches its verdict, so a reviewer sees an infrastructure error instead of a result on the behavior under test.
|
||||
A test runs headless, so every UI call on the executed path must be intercepted by a matching handler named in `[HandlerFunctions(...)]`. The list is a two-sided contract: an unhandled UI call aborts the test, while Microsoft documents that [every listed handler must execute at least once](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/attributes/devenv-handlerfunctions-attribute#remarks) or the test fails.
|
||||
|
||||
Getting the handler *present* is only half the job; the handler must also verify the *right* dialog fired the *right* number of times. Do that by driving handlers from the test, not by hardcoding answers inside them.
|
||||
Beyond that wiring guarantee, the test must verify the behavior it cares about. The appropriate pattern depends on the contract: a handler can capture concrete page state or a result and the test can assert that semantic postcondition after `RunModal`; assertions inside a handler are also supported. Queue/enqueue/dequeue and `LibraryVariableStorage.AssertEmpty` are useful when interaction order, count, text, replies, or a scripted sequence is itself part of the contract, but they are not mandatory for every handler.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Make the test own the expectations and the handlers consume them. Before acting, the test `Enqueue`s — in interaction order — the expected text (a stable substring) and any reply each handler must return. The handler `Dequeue`s the expected text, verifies it with the purpose-built asserts (`Assert.ExpectedMessage`, `Assert.ExpectedConfirm`, `Assert.ExpectedStrMenu` — which match on a fragment, not the full localized caption), then `Dequeue`s and returns its reply. Finish the test body with `LibraryVariableStorage.AssertEmpty` to prove every enqueued interaction fired exactly once, and start each test with an `Initialize` that calls `LibraryVariableStorage.Clear` so a value leaked by an earlier test cannot cascade. List in `[HandlerFunctions]` precisely the handlers the scenario triggers — no superset "just in case", no subset that happens to work today.
|
||||
List precisely the handlers the scenario triggers and make each handler contribute meaningful evidence. For a single modal page, reset a capture variable before the action, capture a concrete value from the page in the handler, and assert the expected value after `RunModal`. For ordered or repeated interactions, let the test enqueue expectations, let handlers dequeue and verify them, clear storage during initialization, and finish with `AssertEmpty`.
|
||||
|
||||
See sample: `ui-handlers-in-tests.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Omitting a handler for a UI call the path raises (unhandled-UI abort), padding the list with a handler the path never reaches ("handler function was not executed"), or writing handlers that hardcode their answer and assert inline with no enqueue/dequeue. The last is the subtle one: nothing proves the correct dialog fired the expected number of times, and an inline assertion that fails inside a handler can be swallowed by the calling UI operation, leaving the suite green while the behavior is broken. Skipping `Initialize`/`AssertEmpty` hides both a leaked queue and a missing or extra dialog.
|
||||
Omitting a handler for a UI call, listing a handler the path never reaches, or claiming action success from a Boolean set before the action runs. A handler that only closes a page can also leave the test without a semantic assertion. Do not flag the absence of queue storage by itself; require it only when the test needs to prove interaction order, count, text, replies, or a scripted sequence.
|
||||
|
||||
See sample: `ui-handlers-in-tests.bad.al`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue