mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-10-05 14:46:55 +01:00
Address Jesper Schulz-Wedde's review on PR #157
- release-must-update-app-version.md: reframe around AppSource's actual
strict full-version-ordering requirement; scope branching-policy
claims as team convention, not platform rule.
- pictures-must-use-media-not-blob.md: MediaSet is a collection of
independent media objects, not automatic image variants/thumbnails.
- log-writes-must-survive-rollback.{md,good.al}: StartSession's only
data channel into the new session is its Record parameter to a
TableNo-scoped codeunit; a setter called on a local instance before
starting the session populates nothing in the new session.
- exposed-objects-must-be-in-a-permission-set.md: correct the three
exposure mechanisms (Web Services config, PageType/QueryType=API,
ServiceEnabled as a method-only attribute).
- pages-must-not-contain-business-logic.md: scope to persisted
mutations and cross-entry-point rules; presentation-only
calculations and table-owned invariants are not violations.
- given-blocks-must-cover-full-precondition-chain.good.al: replace
invented LibrarySales calls with the real API
(CreateCustomer/CreateSalesOrderForCustomerNo/PostSalesDocument).
- test-feature-scenario-tags.{md,good.al}: move [SCENARIO] inside the
test procedure body to match the current BCApps corpus; keep
[FEATURE] at codeunit level per Microsoft's own documented option.
- ui-test-codeunit-naming.md: scope the _UT suffix and adjacent-ID
pairing as an explicit team convention, not a BCApps-wide standard.
- page-design-must-match-bc-page-type-conventions.md /
table-design-must-match-bc-table-type-conventions.md: Card's
single-key primary-key claim is a contextual heuristic, not a
mandatory constraint (Ship-to Address, Customer/Vendor Bank Account
are real composite-key Card pages); a Subsidiary table with its own
identity commonly gets List+Card, not Worksheet/Tabular.
- api-page-least-privilege-write-access.{md,good.al}: only page-placed
fields are ever exposed; set InsertAllowed/DeleteAllowed=false in the
good sample so a narrow field set can't still create/delete records.
- source-organized-by-feature-not-object-type.md,
test-one-when-per-test.md: scope as team/testing-design conventions,
not Microsoft platform requirements.
- upgrade-tag-logic-must-not-nest-deeply.md: add the Microsoft Learn
citation that already backs the two-level nesting limit.
- Wire the new articles into the testing/data-modeling/error-handling/
security/ui review skills' candidate-selection signals.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
a4d85c3e9e
commit
0120b874b2
22 changed files with 111 additions and 58 deletions
|
|
@ -3,13 +3,18 @@ procedure PostSalesOrder_CreatesInvoice()
|
|||
var
|
||||
Customer: Record Customer;
|
||||
SalesHeader: Record "Sales Header";
|
||||
SalesInvoiceHeader: Record "Sales Invoice Header";
|
||||
InvoiceNo: Code[20];
|
||||
begin
|
||||
// [GIVEN] a customer with a full posting-group chain and VAT setup
|
||||
LibrarySales.CreateCustomerWithPostingSetup(Customer);
|
||||
// [GIVEN] a sales order for that customer, dated explicitly
|
||||
LibrarySales.CreateSalesOrderForCustomer(SalesHeader, Customer."No.", WorkDate());
|
||||
// [GIVEN] a customer
|
||||
LibrarySales.CreateCustomer(Customer);
|
||||
// [GIVEN] a sales order for that customer
|
||||
LibrarySales.CreateSalesOrderForCustomerNo(SalesHeader, Customer."No.");
|
||||
SalesHeader.Validate("Posting Date", WorkDate());
|
||||
SalesHeader.Modify(true);
|
||||
// [WHEN]
|
||||
LibrarySales.PostSalesOrder(SalesHeader, false, true);
|
||||
InvoiceNo := LibrarySales.PostSalesDocument(SalesHeader, false, true);
|
||||
// [THEN]
|
||||
SalesInvoiceHeader.Get(InvoiceNo);
|
||||
Assert.RecordIsNotEmpty(SalesInvoiceHeader);
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ codeunit 50103 "Item Price Testing"
|
|||
ItemPriceMgt: Codeunit "Item Price Mgt.";
|
||||
Assert: Codeunit "Library Assert";
|
||||
|
||||
// [SCENARIO] Customer with a specific price list line gets that unit price
|
||||
[Test]
|
||||
procedure GetPrice_CustomerPrice_ReturnsUnitPrice()
|
||||
var
|
||||
|
|
@ -16,6 +15,7 @@ codeunit 50103 "Item Price Testing"
|
|||
Item: Record Item;
|
||||
UnitPrice, LineDiscPct: Decimal;
|
||||
begin
|
||||
// [SCENARIO] Customer with a specific price list line gets that unit price
|
||||
// [GIVEN] a customer with a price list line at 100 LCY
|
||||
LibrarySales.CreateCustomerWithPrice(Customer, Item, '', 100);
|
||||
// [WHEN]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Test codeunits are easier to trust and to review when they carry a four-level co
|
|||
|
||||
## Best Practice
|
||||
|
||||
Put `[FEATURE]` as a comment before the codeunit's opening brace, naming the domain rather than the object. Put `[SCENARIO]` immediately above each `[Test]` attribute, describing the scenario in business language that complements — not duplicates — the procedure name. Inside the body, mark the precondition setup as `[GIVEN]`, the single action under test as `[WHEN]`, and the assertions as `[THEN]`. The procedure name stays the machine-readable identity shown in test-runner output; the `[SCENARIO]` comment stays the human-readable one. Neither replaces the other.
|
||||
Put `[FEATURE]` as a comment before the codeunit's opening brace, naming the domain rather than the object — Microsoft's own guidance allows setting it once for the whole codeunit, inherited by every test in it. Put `[SCENARIO]`, matching the current BCApps corpus, as the first comment inside each test procedure's body (after `begin`), describing the scenario in business language that complements — not duplicates — the procedure name, followed by `[GIVEN]` marking the precondition setup, `[WHEN]` marking the single action under test, and `[THEN]` marking the assertions. The procedure name stays the machine-readable identity shown in test-runner output; the `[SCENARIO]` comment stays the human-readable one. Neither replaces the other.
|
||||
|
||||
See sample: `test-feature-scenario-tags.good.al`.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ application-area: [all]
|
|||
|
||||
## Description
|
||||
|
||||
Each test procedure should contain exactly one `[WHEN]` block: one action that triggers the behaviour under test. A test with multiple WHENs — "do A, then do B, then check C" — is two or more tests in disguise. Splitting them gives failure isolation (a failing test points at one action, not an ambiguous sequence) and keeps each test readable as a single, falsifiable claim. A precondition action, such as posting a document so a ledger entry exists to assert against, belongs in `[GIVEN]`; only the action actually being asserted belongs in `[WHEN]`.
|
||||
This is a testing-design practice, not a BC platform requirement — no AL API enforces it, and it should not gate a change the way a platform-contradicted claim would. Each test procedure should contain exactly one `[WHEN]` block: one action that triggers the behaviour under test. A test with multiple WHENs — "do A, then do B, then check C" — is two or more tests in disguise. Splitting them gives failure isolation (a failing test points at one action, not an ambiguous sequence) and keeps each test readable as a single, falsifiable claim. A precondition action, such as posting a document so a ledger entry exists to assert against, belongs in `[GIVEN]`; only the action actually being asserted belongs in `[WHEN]`.
|
||||
|
||||
## Best Practice
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: testing
|
||||
keywords: [ui-test, testpage, naming, suffix, codeunit, page-testing]
|
||||
keywords: [ui-test, testpage, naming, suffix, codeunit, page-testing, team-convention]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Suffix UI-layer test codeunits with _UT and never mix layers in one codeunit
|
||||
# Separate UI-layer and logic-layer tests into different codeunits
|
||||
|
||||
## Description
|
||||
|
||||
A test codeunit that drives pages through `TestPage` — opening pages, reading FactBox parts, triggering field `OnValidate` through the page — is testing a different layer than a codeunit that calls business-logic procedures directly. Readers need to know which layer a given test exercises without opening it, and a single codeunit that mixes both kinds of test hides that distinction: a failure could mean the logic broke, the page broke, or both.
|
||||
A test codeunit that drives pages through `TestPage` — opening pages, reading FactBox parts, triggering field `OnValidate` through the page — is testing a different layer than a codeunit that calls business-logic procedures directly. Readers need to know which layer a given test exercises without opening it, and a single codeunit that mixes both kinds of test hides that distinction: a failure could mean the logic broke, the page broke, or both. The `_UT` suffix and adjacent-object-ID pairing below are one team's naming convention for making that split visible, not a BCApps-wide naming standard — BCApps itself uses `UT` for unit tests generally, not specifically to mean "UI layer," and does not treat adjacent object IDs as a semantic pairing mechanism. Apply the suffix only on a project that has explicitly adopted this convention.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Give any test codeunit that uses `TestPage` a `_UT` (Unit Test — UI layer) suffix, and keep it free of tests that call codeunit/table procedures directly. Keep the corresponding logic-only codeunit unsuffixed. Allocate the two codeunits adjacent object IDs so their relationship is visible in the object list.
|
||||
Keep UI-layer (`TestPage`-driven) and logic-layer tests in separate codeunits regardless of naming. Projects that adopt a `_UT`-style suffix convention should apply it consistently to every UI-layer test codeunit, keep the corresponding logic-only codeunit unsuffixed, and document the convention where the team's other naming rules live.
|
||||
|
||||
See sample: `ui-test-codeunit-naming.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
A codeunit named without the `_UT` suffix that nonetheless contains `TestPage` calls, or — worse — one codeunit that mixes a direct logic-call test and a `TestPage`-driven test side by side. Either way, the codeunit's name no longer tells a reader which layer a failing test actually broke.
|
||||
One codeunit that mixes a direct logic-call test and a `TestPage`-driven test side by side — a failing test no longer tells a reader which layer actually broke. On a project that has adopted the `_UT` convention, a UI-layer codeunit missing the suffix is also an instance of this anti-pattern; on a project that has not adopted it, the suffix itself is not required.
|
||||
|
||||
See sample: `ui-test-codeunit-naming.bad.al`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue