Skip to main content

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)NamespaceViewer location
[Feature("Checkout")] on CheckoutFeatureMyApp.Tests.OrdersMyApp → Tests → Orders → Checkout
[Specification("Shipping rates")] on ShippingRatesSpecificationMyApp.Tests.Orders.PricingMyApp → Tests → Orders → Pricing → Shipping rates
[Feature("Registration")] on RegistrationFeatureMyApp.Tests.AccountsMyApp → 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:

  1. Intent — The .Spec.ts suffix 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.

  2. Coexistence — You can have both .Spec.ts files (living documentation) and .test.ts files (standard tests) in the same project. Vitest discovers them separately based on config patterns.

  3. 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)
Customizable

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❌ AvoidWhy
Login.Spec.tslogin.spec.tsDifferent convention — only matches if your vitest include pattern allows it
ShoppingCartCheckout.Spec.tstest-checkout.Spec.ts"test" prefix adds no value
PasswordStrengthValidator.Spec.tsPSV.Spec.tsAbbreviations are unclear in reports
EmailNotifications.Spec.tsemail_notifications.Spec.tsUnderscores 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
LoginFeatureLoginTests
ShoppingCartCheckoutFeatureCheckoutTest
PasswordStrengthSpecificationPasswordSpecs
EmailNotificationFeatureTestEmail

Directory/Namespace Names​

Use domain terms, not technical terms:

✅ Good (Domain)❌ Avoid (Technical)
AuthControllers
CartServices
BillingRepositories
NotificationsHandlers
UserManagementModels

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.ts convention 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​