Test Organization
In LiveDoc, your test structure becomes the reader's table of contents. Group Features and Specifications by the business capability they explain, so readers can find a workflow and its detailed rules together. File paths (TypeScript) and namespaces (C#) determine the hierarchy in the Viewer and generated reports.
Why Organization Matters
Traditional test frameworks don't care much about how you organize tests — they find files matching a pattern, run everything, and report pass/fail. But LiveDoc is different. Because your tests are living documentation, the organizational structure becomes the table of contents for your system's specification.
Consider two ways to organize the same tests:
Organization A (by domain): Organization B (by type):
tests/ tests/
├── Auth/ ├── unit/
│ ├── Login.Spec.ts │ ├── loginValidator.test.ts
│ ├── Registration.Spec.ts │ ├── cartCalc.test.ts
│ └── PasswordReset.Spec.ts │ └── emailFormat.test.ts
├── Cart/ ├── integration/
│ ├── AddToCart.Spec.ts │ ├── loginFlow.test.ts
│ └── Checkout.Spec.ts │ ├── cartCheckout.test.ts
└── Notifications/ │ └── emailSend.test.ts
├── EmailAlerts.Spec.ts └── e2e/
└── PushNotifications.Spec.ts ├── fullLogin.test.ts
└── fullCheckout.test.ts
Organization A produces documentation grouped by what the system does — Authentication, Cart, Notifications. A product owner can navigate to "Cart" and see all cart-related specifications. Organization B produces documentation grouped by how the tests run — unit, integration, e2e. That's useful for developers but meaningless for specification readers.
LiveDoc is opinionated: organize by domain, not by technical layer or test pattern. An Orders Feature can explain how checkout works while an Orders Specification shows the exact shipping calculations. A product manager may read both. Feature and Specification describe the shape of the proof, not who is allowed to read it.
Starting with Features/Orders and Specifications/Orders instead creates
two Orders branches in a full report. Keep Orders together and add a
reader-friendly subgroup such as Pricing when the capability grows.
How Structure Maps to Reports
TypeScript/Vitest: File Path → Hierarchy
In the Vitest SDK, the file path determines the report hierarchy. Each
directory below the run's shared root becomes a navigation group; each
feature() or specification() becomes a document. The .Spec.ts suffix is
used for both patterns:
tests
├── Accounts
│ └── Registration.Spec.ts feature("Registration")
└── Orders
├── Checkout.Spec.ts feature("Checkout")
└── Pricing
└── ShippingRates.Spec.ts specification("Shipping rates")
The path is captured in the reporting model as
the path field on each TestCase. The Viewer uses it to group documents
by directory.
This example assumes a full run across both capabilities: the Vitest reporter strips the shared parent directory of the files included in that run. A single-file or filtered run can therefore show a shorter path.
C#/xUnit: Namespace → Hierarchy
In the xUnit SDK, the full declared namespace, not the physical folder alone, serves the same purpose as the Vitest file path. All namespace segments are retained, independently of the assembly name:
| Class (assembly name can differ) | Namespace | Viewer location |
|---|---|---|
[Feature("Checkout")] on CheckoutFeature | MyApp.Tests.Orders | MyApp → Tests → Orders → Checkout |
[Specification("Shipping rates")] on ShippingRatesSpecification | MyApp.Tests.Orders.Pricing | MyApp → Tests → Orders → Pricing → Shipping rates |
[Feature("Registration")] on RegistrationFeature | MyApp.Tests.Accounts | MyApp → Tests → Accounts → Registration |
The final item in each Viewer location is the document title, not another
folder. For example, CheckoutFeature produces the report path
MyApp/Tests/Orders/CheckoutFeature.cs even when its assembly is named
MyApp.Tests. Renaming the assembly does not change that path. Class names
remain .cs leaves, titles label those documents, and namespace-free classes
remain documents at the root.
Keep the source folders and declared namespaces aligned so developers and report readers follow the same capability structure.
The Principle
Both SDKs follow the same principle: your organizational structure becomes the reader's navigation structure. The attributed xUnit class or top-level Vitest Feature/Specification is the document; its title names the behavior, and its path places it under the capability.
The .Spec.ts Convention (TypeScript/Vitest)
The LiveDoc SDK itself is file-name agnostic — it doesn't enforce any naming convention. What controls which files are discovered is your vitest config's include pattern. The recommended convention is .Spec.ts:
// vitest.config.ts
export default defineConfig({
test: {
include: ['**/*.Spec.ts'], // ← This controls discovery, not the SDK
},
});
This convention serves several purposes:
-
Intent — The
.Spec.tssuffix signals living documentation, whether the file contains a Feature or a Specification. It's a reminder to write self-documenting tests with values in titles. -
Coexistence — You can have both
.Spec.tsfiles (living documentation) and.test.tsfiles (standard tests) in the same project. Vitest discovers them separately based on config patterns. -
Team clarity — A distinct suffix makes it easy to distinguish specification files from regular test files in your project tree.
tests/
├── Auth/
│ ├── Login.Spec.ts ← LiveDoc specification (Feature/Scenario)
│ ├── Login.test.ts ← Standard Vitest tests (unit tests, helpers)
│ └── authHelpers.ts ← Test utilities (not executed directly)
You can use any naming convention you prefer — .spec.ts, .feature.ts, or even .test.ts — by changing the include pattern in your vitest config. The .Spec.ts convention (capital S) is recommended because it visually distinguishes LiveDoc specifications from standard test files, but it's not a technical requirement.
Naming Conventions
File Names (TypeScript)
Use PascalCase file names that describe the feature or specification:
| ✅ Good | ❌ Avoid | Why |
|---|---|---|
Login.Spec.ts | login.spec.ts | Different convention — only matches if your vitest include pattern allows it |
ShoppingCartCheckout.Spec.ts | test-checkout.Spec.ts | "test" prefix adds no value |
PasswordStrengthValidator.Spec.ts | PSV.Spec.ts | Abbreviations are unclear in reports |
EmailNotifications.Spec.ts | email_notifications.Spec.ts | Underscores don't match convention |
The file name (minus .Spec.ts) becomes the default document title in the report if no feature() or specification() title overrides it.
Class Names (C#)
Use PascalCase class names with a Feature or Specification suffix:
| ✅ Good | ❌ Avoid |
|---|---|
LoginFeature | LoginTests |
ShoppingCartCheckoutFeature | CheckoutTest |
PasswordStrengthSpecification | PasswordSpecs |
EmailNotificationFeature | TestEmail |
Directory/Namespace Names
Use domain terms, not technical terms:
| ✅ Good (Domain) | ❌ Avoid (Technical) |
|---|---|
Auth | Controllers |
Cart | Services |
Billing | Repositories |
Notifications | Handlers |
UserManagement | Models |
Your test structure should mirror how a user thinks about the system, not how the code is architectured. A user thinks about "Authentication" and "Shopping Cart" — they don't think about "Controllers" and "Services."
Descriptions Enhance Context
Both Features and Specifications support optional descriptions — multi-line text that explains why the tested behavior matters. The title names the behavior; Scenario steps or Rules and assertions establish what happened. Avoid inventories of tested cases and outcomes beyond the test's observable boundary. In the reporting model, the description appears beside the title in the Viewer:
TypeScript/Vitest
feature(`User Login
@auth @critical
Keeps sign-in decisions consistent for the email/password and
OAuth paths covered by the scenarios below.
`, (ctx) => {
// scenarios...
});
C#/xUnit
[Feature("User Login", Description = @"
Keeps sign-in decisions consistent for the email/password
and OAuth paths covered by the scenarios below.")]
[Tag("auth, critical")]
public class LoginFeature : FeatureTest
{
// scenarios...
}
How Descriptions Appear in Reports
Auth
└── User Login @auth @critical
Keeps sign-in decisions consistent for the email/password
and OAuth paths covered by the scenarios below.
✓ Scenario: Successful login with email and password
✓ Scenario: Login with Google OAuth
✗ Scenario: Login with expired session token
The description adds context that the title alone can't convey. Use it for:
- Business context — Why this feature exists, who uses it
- Scope boundaries — What this specification covers and what it doesn't
- Dependencies — What other features or systems are involved
Tags for Cross-Cutting Organization
Tags provide a secondary organization axis that cuts across the directory/namespace hierarchy:
@critical — Business-critical features (must not regress)
@smoke — Quick sanity checks for CI
@slow — Tests that take longer to run
@wip — Work in progress (incomplete specifications)
@sprint-12 — Traceability to project management
@pci — Compliance-related specifications
Tags appear in the Viewer's filter controls, allowing readers to view specifications by concern rather than by domain. A compliance officer might filter by @pci to see all PCI-related specifications regardless of which feature area they belong to.
Both SDKs support tags in the same way — as strings attached to Features, Specifications, Scenarios, and Rules.
Best Practices
1. Group Both Patterns by Capability
Recommended:
tests\Orders\Checkout.Spec.ts Feature
tests\Orders\Pricing\ShippingRates.Spec.ts Specification
Avoid as the default:
tests\Features\Orders\Checkout.Spec.ts
tests\Specifications\Orders\ShippingRates.Spec.ts
The report should read like a product specification, not a deployment diagram or two separate catalogues of the same product area. Use a distinct cross-domain capability for shared platform contracts rather than forcing them into Orders.
2. Mirror the Product, Not the Code
If your application has modules called "Authentication", "Inventory", and "Payments", your test folders should use the same names. Don't use the code's internal naming (e.g., AuthService, InventoryRepository) — use the product's vocabulary.
3. Keep the Hierarchy Shallow
Two to three levels of nesting is usually sufficient. Deep hierarchies create navigation that's hard to browse:
✅ tests/Auth/Login.Spec.ts (2 levels: Auth > Login)
✅ tests/Cart/Discounts/Coupons.Spec.ts (3 levels: Cart > Discounts > Coupons)
❌ tests/Features/Core/Auth/Login/Happy/BasicLogin.Spec.ts (6 levels — too deep)
4. One Feature or Specification per File
Prefer one feature() or specification() call per .Spec.ts file. This
keeps the mapping between files and documents clean and predictable:
// Login.Spec.ts — one feature
feature("User Login", (ctx) => {
scenario("Successful login", (ctx) => { ... });
scenario("Failed login", (ctx) => { ... });
});
Having multiple features in a single file creates confusion about where a specification lives and makes the file-to-document mapping ambiguous.
5. Consider the Report When Choosing Structure
Before creating a new file or folder, visualize how it will appear in the Viewer. Ask:
- Will readers find this where they expect it?
- Does the navigation path make sense? (e.g., "Cart > Checkout > Payment Methods")
- Is the grouping meaningful to someone who hasn't read the code?
6. Use Consistent Naming Across the Team
Agree on naming conventions early and document them. Inconsistency (some files use singular Auth, others use plural Users) creates a messy navigation tree.
Visual Example: From Files to Report
For the full-run source layout above, either SDK produces this reader-facing hierarchy when its xUnit namespaces or Vitest paths match the capability:
Accounts
Registration Feature
Register with email Scenario
Orders
Pricing
Shipping rates Specification
Fee for '<region>' at '<total>' is '<fee>' RuleOutline
Checkout Feature
Place an eligible order Scenario
Someone exploring Orders can read the Checkout narrative first, then inspect the Shipping rates calculation examples without leaving Orders. The Feature proves the workflow it observes; the Specification owns the exact rule and its boundary cases. Neither needs to repeat every assertion in the other.
Recap
- File path (TypeScript) and namespace (C#) determine the documentation hierarchy in the Viewer.
- Organize by capability first, with its Features and Specifications together, not separate test-type or execution-layer roots.
- A Specification may document a business-owned rule for product readers; choose the pattern by whether narrative steps or direct rules communicate the claim best.
- The
.Spec.tsconvention covers both patterns in TypeScript and is configurable via your vitest config. - Use PascalCase names that describe features, not test infrastructure.
- Descriptions and tags add context and enable cross-cutting organization.
- Keep hierarchies shallow (2–3 levels) for easy navigation.
- Put one feature/specification per file for clean file-to-document mapping.
- Always consider the report when choosing structure — your readers navigate what you create.
Next Steps
- Next in this series: Reporting Model — the data structure that connects SDKs to the Viewer
- Hands-on: Tutorial: Beautiful Tea — build a complete living specification from scratch
- SDK specifics: Vitest Getting Started or xUnit Getting Started