-
Notifications
You must be signed in to change notification settings - Fork 70
Regression Testing
The add-in ships with a generalized regression-test harness for verifying that database objects survive a serialize / deserialize cycle unchanged. This page covers what it does, how to run it, and how to contribute new fixtures when you hit an edge case.
Every change to the export / import pipeline (sanitization rules, formatter quirks, JSON layout, edge-case handling) carries the risk of breaking some object somewhere. With thousands of users and an open-ended object grammar, the only sustainable way to defend against regressions is a corpus of real-world fixtures that can be re-checked on every change.
The harness is generic over IDbComponent, so the same machinery covers queries today and forms, reports, modules, table data, etc. as fixtures are added.
For each fixture under Testing/Fixtures/, the harness:
- Imports the fixture into the running database under a sandboxed name (
vcs_test_<basename>_<hash>). - For queries, validates generated
.qdefstructure:-
qdef_joins— each join row's tables match itsExpression(Design View structural check). -
qdef_vs_fixture— compares generated.qdefto a stored.qdefbaseline when present.
-
- Exports twice (Pass 1 and Pass 2) into a per-run scratch folder.
- Asserts Pass 2 == Pass 1 (idempotency — hard requirement).
- Asserts Pass 1 == fixture (drift check — soft requirement; warnings when rebaselining).
- Drops the sandboxed object and refreshes the cache (
DBEngine.Idle dbRefreshCache) before moving on.
Stale sandbox objects from a prior crashed run are detected and cleaned up at the start of every session, so the database does not accumulate cruft over time.
Open frmVCSMain first if you want live progress in the console.
?VCS.RunRoundtripTestsTo run against your own corpus instead of the shipped fixtures, pass a folder path:
?VCS.RunRoundtripTests("C:\path\to\my-fixtures\")To rebaseline (overwrite fixtures with the actual export when comparisons mismatch — review the resulting git diff carefully before committing):
?VCS.RunRoundtripTests(, True)vcs_run_vba(<addin-path>, "MCP_TempFunction = VCS.RunRoundtripTests()")
This requires Allow Arbitrary VBA Execution (McpAllowRunVBA) under Options → MCP. See MCP and Automation.
For unit tests (not round-trip), use Testing and VCS.RunTests.
Inside the add-in's own development VBE, the harness is also callable via ?modTestRoundtrip.RunObjectRoundtripTests() for in-project debugging.
The harness produces output on three coordinated channels:
| Channel | Audience | Contents |
|---|---|---|
frmVCSMain console |
Developer running interactively | One line per fixture (✔/✖), running totals, summary block |
Testing/Fixtures/logs/ObjectRoundtrip_<opId>.log |
Post-mortem inspection | Full log with unified diffs for every failure |
| JSON return value | CI / vcs_run_vba callers |
Machine-parseable summary with per-fixture results, hashes, and diff payloads |
The JSON shape is:
{
"success": true,
"fixtureFolder": "...",
"scratchFolder": "...",
"logPath": "...",
"stats": { "total": 15, "passed": 15, "failed": 0, "skipped": 0, "errors": 0, "elapsedSeconds": 4.2 },
"results": [
{ "fixture": "qryCars", "category": "select", "status": "pass", "checks": [...] },
...
]
}Testing/Fixtures/
├── README.md ← contributor-facing usage
├── .gitignore ← excludes scratch/ and logs/
├── _scaffold/ ← shared supporting objects (loaded once per session)
│ └── .gitkeep
├── queries/
│ ├── select/ ← qryCars.sql + qryCars.json, ...
│ ├── crosstab/
│ ├── append/
│ ├── update/
│ ├── delete/
│ ├── union/
│ ├── passthrough/
│ ├── ddl/
│ └── regression/ ← bug-fix pin-downs, with sibling .notes.md files
├── scratch/ ← per-run output (gitignored)
└── logs/ ← per-session log files (gitignored)
The _scaffold/ folder is a convention for fixtures that depend on shared tables or supporting queries. Anything in _scaffold/ is imported once at the start of a run and dropped at the end. v1 doesn't ship any scaffold objects (the shipped corpus exercises only standalone queries), but the convention is in place for future fixtures that need it.
This is the contribution pattern the harness was built around. When you hit an object that fails to round-trip:
Confirm the bug reproduces with the current add-in. If you can isolate it to a single object, great — that's your fixture candidate.
Strip anything you can't share publicly:
- Replace business-sensitive table / field / query names with generic ones (
tblCustomer→tblA,lngCustomerId→lngId). - Replace
Connectstrings on linked tables withenv:references or remove the linked tables entirely if they aren't required to reproduce the bug. - Remove embedded sample data unless the bug is data-dependent.
- Strip comments that reference internal projects or people.
The goal is the smallest possible reproducer that still fails the round-trip in the same way.
The sanitized <name>.sql + <name>.json pair goes into the most appropriate subfolder under Testing/Fixtures/queries/. If you're pinning down a specific bug, use regression/.
Sibling markdown documenting the bug. Format:
# qryYourFixture
**Bug:** Brief description of the failure mode.
**Symptom:** What the user sees (compile error, runtime error, silent corruption, etc.).
**Root cause:** Brief explanation if known.
**Fixed in:** Issue / PR link.Title it regression: <one-line description> against dev. The fixture itself is the test — once it passes locally, it'll guard against the bug forever.
The unique enabler here is the add-in's text-source format. Because every Access object can be expressed as a .sql + .json (or .bas, .cls, .form, etc.) pair, a "bug report" and a "regression test" are literally the same artifact. Other Access projects can't easily do this because they lack a canonical text representation of database objects.
This means:
- Bug reproduction is a file copy, not a screen recording.
- Regression coverage grows organically with the user base, not just with the maintainers' bandwidth.
- The corpus doubles as a portable benchmark for evaluating proposed changes to the export pipeline (you can rerun it across versions).
v1 supports queries. To add support for forms (or any other IDbComponent):
- Create a
Testing/Fixtures/forms/subtree mirroring thequeries/layout. - Add a
RunFormFixtureshelper tomodTestRoundtrip.basmodeled onRunQueryFixtures. It needs to know how to enumerate the fixture pairs, import them under a sandboxed name, export twice, compare, and clean up. Most of the existing helpers (ProvisionScratchFolder,MakeUnifiedDiff,AddCheck,RollUpStatus,LogFixtureResult) are component-agnostic and reusable. - Wire the new helper into
RunObjectRoundtripTests.
The public API method (VCS.RunRoundtripTests) doesn't change — it's deliberately component-agnostic.
See modTestRoundtrip.bas for the existing query implementation as a template.