3 AL/BC patterns from CURABIS's internal automated-testing training material (#158)

* Add 3 more AL/BC patterns from CURABIS Academy testing course material

Third batch from CURABIS ApS: item-ledger-entry document-no lookup after Ship-and-Invoice posting, TestPage.Visible()/.Enabled() as the mechanism for verifying field UI state, and LibraryUtility.GenerateGUID() for collision-free test fixture values.

* Address Jesper Schulz-Wedde's review on PR #158

- use-generateguid-for-unique-test-fixture-values.md: GenerateGUID()
  is a Code[10] number-series value, not a real GUID; truncating it
  with CopyStr for a shorter field cuts off the changing digits. Point
  to GenerateRandomCode/GenerateRandomCodeWithLength/GenerateRandomXMLText
  instead, which verify uniqueness against the actual table.
- Split use-testpage-visible-enabled-to-verify-field-ui-state.md: drop
  its editability claim (the sample opens with OpenView() and asserts
  Enabled(), which verifies enabled state, not editability — Editable()
  and Enabled() are distinct TestField methods). New companion article
  use-testpage-editable-to-verify-field-editability.md covers Editable()
  with OpenEdit() specifically.
- Wire GenerateGUID/CopyStr and TestPage Visible/Enabled/Editable cues
  into al-testing-review.md, and the Item Ledger Entry/Last Shipping No.
  posting cue into al-data-modeling-review.md.

The Item Ledger Entry article itself was independently verified against
current BCApps source and needs no changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Address second round of Jesper Schulz-Wedde's review on PR #158

- use-generateguid-for-unique-test-fixture-values.md/.good.al: documented
  each LibraryUtility helper's actual behavior, verified against
  LibraryUtility.Codeunit.al. GenerateRandomCode opens the target table as
  a temporary RecordRef, so despite taking TableNo it never checks real
  data. GenerateRandomXMLText performs no table lookup at all. Only
  GenerateRandomCodeWithLength/GenerateRandomCode20 (capped at Code[10]/
  Code[20]) genuinely verify against the real table. Fixture switched to
  GenerateRandomCodeWithLength where the comment claims verified
  uniqueness.
- al-testing-review.md: rewired the cue to catch the actual anti-pattern
  (hardcoded literals, hand-built uniqueness, short-field GUID truncation)
  instead of only matching the compliant GenerateGUID()+CopyStr shape;
  broadened tokens to include TestPage, Library - Utility, and
  .Visible()/.Enabled()/.Editable().
- al-data-modeling-review.md: restricted the Item Ledger Entry
  Last-Shipping-No. cue to sales combined posting; purchase combined
  posting is Receive+Invoice and uses different fields entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Fix remaining correctness issues from Jesper's 2026-09-15 re-review

- use-generateguid-for-unique-test-fixture-values.md: narrowed the
  collision rule to primary-key/unique-lookup fields - an ordinary
  descriptive field carries no uniqueness constraint, so a hardcoded
  or deterministic value there isn't the anti-pattern (the article and
  its al-testing-review.md worklist cue both said "primary-key or
  descriptive field"). Also corrected GenerateRandomCode: it opens its
  target table as a temporary RecordRef that starts and stays empty,
  so its repeat/until loop always exits after one iteration and never
  retries even within a single test run - the "non-colliding within a
  test run" claim was false. It's the rightmost N characters of
  GenerateGUID()'s sequential series, so a short field's value cycles
  (Code[1] repeats every 10 calls, Code[2] every 100). Verified against
  LibraryUtility.Codeunit.al in the BCApps reference clone.
- item-ledger-entry-document-no-follows-last-shipping-no: both
  fixtures called FindSet() without consuming its optional Boolean,
  which raises a runtime error on an empty result set - the opposite
  of the article's own claimed "silently matches zero rows, no error"
  behavior. Wrapped in `if ... then;` per the existing
  guard-database-reads.good.al idiom.
- al-data-modeling-review.md: widened both not-applicable scope
  clauses (intro and outcome) to include dimension wiring, posting-
  routine structure, and Item Ledger Entry document-number lookups -
  the leaf declared itself not-applicable outside setup/master/key/
  numbering/block/audit surfaces despite having a targeted cue for
  this PR's own new article.
- Converted this PR's 8 plain-backtick "See sample: `x.good.al`."
  references (across all 4 new articles) to the READ-convention
  markdown-link form required by Knowledge-Retrieval.ps1.

Rebased onto upstream/main (one conflict in al-data-modeling-review.md
intro wording, merged).

* Stop routing GenerateRandomCode20 as compliant for shorter fields

al-testing-review.md's cue presented GenerateRandomCodeWithLength and
GenerateRandomCode20 as interchangeable options for "a shorter field
needing real verified uniqueness." They aren't: verified against
LibraryUtility.Codeunit.al, GenerateRandomCode20 truncates
GenerateGUID()'s sequential value down to the target field's length by
keeping the leftmost (slowest-changing) characters via PadStr, so its
retry loop against a field shorter than 20 can churn through the same
truncated prefix for a long time. GenerateRandomCodeWithLength has no
such problem (it generates exactly the requested length of random
text). The knowledge article itself already scoped GenerateRandomCode20
to Code[20] correctly - only the skill cue needed narrowing to match.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Michael Dieringer 2026-09-29 12:49:11 +02:00 • committed by GitHub
parent 0a8c9a8556
commit 186691f815
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 255 additions and 3 deletions

View file

@ -39,7 +39,7 @@ Narrow the relevant files to the subset that applies to the changes under review
- The changed AL object names and types — especially codeunits with `Subtype = Test`, test runner codeunits with `TestIsolation`, test libraries, and codeunits that define UI handlers.
- The changed methods and attributes, weighted toward `[Test]`, `[TransactionModel(...)]`, `[TestPermissions(...)]`, `[HandlerFunctions(...)]`, handler attributes, `asserterror`, `ExpectedError`, `ExpectedErrorCode`, fixture initialization, and test-library calls.
- Tokens extracted from the diff that relate to testing (`Subtype = Test`, `Subtype = TestRunner`, `TestIsolation`, `TestPermissions`, `Restrictive`, `NonRestrictive`, `Disabled`, `Permissions Mock`, `Library - Lower Permissions`, `TransactionModel`, `AutoRollback`, `AutoCommit`, `Commit`, `asserterror`, `ExpectedError`, `ExpectedErrorCode`, `HandlerFunctions`, `ConfirmHandler`, `MessageHandler`, `StrMenuHandler`, `ModalPageHandler`, `SendNotificationHandler`, `RecallNotificationHandler`, `Enqueue`, `Dequeue`, `AssertEmpty`, `Library Assert`, `LibraryVariableStorage`, `LibrarySales`, `LibraryPurchase`, `LibraryERM`, `LibraryInventory`, `LibraryRandom`, `Init`, `Insert`).
- Tokens extracted from the diff that relate to testing (`Subtype = Test`, `Subtype = TestRunner`, `TestIsolation`, `TestPermissions`, `Restrictive`, `NonRestrictive`, `Disabled`, `Permissions Mock`, `Library - Lower Permissions`, `TransactionModel`, `AutoRollback`, `AutoCommit`, `Commit`, `asserterror`, `ExpectedError`, `ExpectedErrorCode`, `HandlerFunctions`, `ConfirmHandler`, `MessageHandler`, `StrMenuHandler`, `ModalPageHandler`, `SendNotificationHandler`, `RecallNotificationHandler`, `Enqueue`, `Dequeue`, `AssertEmpty`, `Library Assert`, `LibraryVariableStorage`, `LibrarySales`, `LibraryPurchase`, `LibraryERM`, `LibraryInventory`, `LibraryRandom`, `Library - Utility`, `LibraryUtility`, `GenerateGUID`, `GenerateRandomCode`, `TestPage`, `.Visible(`, `.Enabled(`, `.Editable(`, `OpenNew`, `OpenView`, `OpenEdit`, `Init`, `Insert`).
A file enters the candidate worklist when its `keywords` intersect the extracted tokens or its topic (derived from the index entry's `path`, `title`, and `description`) matches a changed object type. Read an article's full file — its `## Best Practice` / `## Anti Pattern` bodies — only after it makes the worklist; candidate selection uses the index alone. When the diff contains no testing-related changes by any of the above signals, return `outcome: "not-applicable"` without evaluating files.
@ -50,6 +50,8 @@ The following targeted checks cover every current `testing` article. Treat each
- A permission-sensitive test uses `TestPermissions = Disabled`, claims to test a restricted user without `"Permissions Mock"`/`"Library - Lower Permissions"`, or declares `[TestPermissions(...)]` without applying that context — `permission-tests-must-lower-the-execution-context`.
- Test fixture code manually calls `Init`/`Insert`, invents keys or prerequisite records, or bypasses available `LibrarySales`, `LibraryPurchase`, `LibraryERM`, `LibraryInventory`, `LibraryRandom`, or equivalent library codeunits — `use-library-codeunits-for-test-fixtures`.
- `asserterror` is added or changed without a following `Assert.ExpectedError`, `Assert.ExpectedErrorCode`, or a purpose-built assertion such as `ExpectedTestFieldError` — `asserterror-needs-expectederror-and-code`.
- Test fixture code assigns a hardcoded literal to a primary-key field or a field the test relies on as a unique lookup identifier, hand-builds a "unique" value for such a field (string concatenation, a counter, `Format(CurrentDateTime)`), or truncates `LibraryUtility.GenerateGUID()`'s result with `CopyStr` for such a field shorter than 10 characters — `use-generateguid-for-unique-test-fixture-values`. Calling `GenerateGUID()` untruncated into a full-length field, `GenerateRandomCodeWithLength` for a shorter field needing real verified uniqueness, or `GenerateRandomCode20` specifically for a `Code[20]` field, is the compliant shape, not the signal to flag. `GenerateRandomCode20` is not a substitute for `GenerateRandomCodeWithLength` on a shorter field — it truncates `GenerateGUID()`'s sequential value down to the field's length by keeping the *leftmost* characters, which change the slowest, so retries against a short field can churn through the same truncated prefix far longer than `GenerateRandomCodeWithLength`'s equivalent. A hardcoded or deterministic value in an ordinary descriptive field is not this anti-pattern — that field carries no uniqueness constraint. Do not claim `GenerateRandomCode` (without `WithLength`/`20`) or `GenerateRandomXMLText` verify uniqueness against the real table, or that `GenerateRandomCode` is collision-free even within one test run for a short field — none of that is true.
- A test asserts against a `TestPage` field's `.Visible()` or `.Enabled()` — `use-testpage-visible-enabled-to-verify-field-ui-state`. When the assertion is against `.Editable()`, or the page is opened with `OpenEdit()` specifically to check editability — `use-testpage-editable-to-verify-field-editability`.
- A test path raises UI and `[HandlerFunctions(...)]` does not match the invoked handlers, or the test has no meaningful evidence of the UI result (for example, it treats a Boolean set before the action as proof of success) — `ui-handlers-in-tests`. A capture/reset/assert-after-`RunModal` pattern is valid. Enqueue/dequeue and `AssertEmpty` are required only when order, count, text, replies, or a scripted sequence is part of the contract. Only nonoptional handlers have to execute: a listed handler declared `[SendNotificationHandler(true)]` or `[RecallNotificationHandler(true)]` is optional by design, so do not treat it as unmatched when the run never raises the notification.
- A test's `[GIVEN]`/setup looks up a hardcoded code/number/name assumed to already exist instead of creating it, leaves a mandatory field on a created record empty, uses a value that doesn't satisfy the scenario's own explicit length/format requirement (for example a truncation test whose value never exceeds the field), or a scenario-defining value (amount, quantity, percentage, date, threshold, rounding precision) is generated/randomized instead of an explicit chosen value — `test-data-must-be-random-and-complete`. Generating incidental fixture values (identifiers, names, descriptions) via the standard library codeunits is the compliant shape, not the signal to flag, and neither is a short-but-valid value in an otherwise-unremarkable field.