AL test codeunits are marked Subtype = Test and execute inside a transaction that rolls back when the run finishes — so a test can post a real invoice and leave the database untouched. Wire them into AL-Go, Microsoft's official pipeline framework, and the build fails before a breaking schema change reaches a tenant.
Why this matters more on SaaS
On-premises, a bad deployment is recoverable: restore the database and try again. On Business Central online you do not have that option. The extension either upgrades the tenant cleanly or it does not, and a schema change that cannot be applied leaves the customer stuck between versions.
That asymmetry is the whole argument for a pipeline. The point is not developer convenience; it is that the only safe place to discover a breaking change is before publish.
What a test codeunit looks like
Two things carry the weight here. Subtype = Test gives the automatic rollback, so the test can exercise the real posting routine and not a mock of it. The Library - Assert codeunit gives failures that name what was expected, which is the difference between a red build you can act on and one you have to investigate.
What is worth testing
| Area | Test | What it prevents |
|---|---|---|
| Posting routines | Post a document, assert the resulting ledger entries. | Silent posting to the wrong account. |
| Tax and pricing | Boundary values, each rate band, rounding edges. | Errors that are tiny per document and large per period. |
| Integration boundaries | Malformed payload, timeout, duplicate delivery. | Partial writes when an external system misbehaves. |
| Permissions | Run as a restricted user, assert the failure. | Features that only work for developers. |
The pipeline
AL-Go orchestrates the containerised build, runs the suites and compares the new app against the last released artifact. Use it instead of writing your own; it is maintained against Business Central's release cadence, and a bespoke pipeline is a second product you now own.
The breaking-change gate
App validation compares the candidate against the previous production artifact and fails the build on changes that cannot be applied to existing data — a deleted field, a shortened text length, an altered data type.
- Mark the object
ObsoleteState = Pendingwith anObsoleteReason. - Ship at least one release carrying the pending state, so dependent extensions get a warning.
- Only then remove it, in a later release.
A deprecation window costs one release. Skipping it costs a support incident on a tenant that cannot upgrade, which is the most expensive bug shape in this ecosystem.
What changes once it is running
The visible change is that releases stop being events. Work merges, the pipeline proves it, and publishing is routine. The change that matters more is quieter: nobody is deciding at the end of a long day whether this one is safe enough to ship.