Skip to content

Testing standard ​

Standard scope

Applies to: All HCS-governed repos that ship executable code or automation scripts Domain: testing Status: Active

All HCS projects that ship executable code or automation scripts require tests. This document defines the test classes, required files, frameworks, and enforcement model.


Canonical Tooling ​

In addition to the base testing standard, HCS Platform Engineering provides two canonical testing frameworks:

  • MAPROOM: Offline fixture contract-testing framework.
  • TRAILHEAD: End-to-end scenario testing harness.

Both frameworks are maintained in this repository at testing/ and are automatically invoked by the reusable ADO pipelines. All HCS repos are encouraged to adopt these tools.


Test classification ​

Every test falls into exactly one of these classes. The class determines where it lives, what runs it, and what a failure means.

ClassPurposeLocationFramework
unitExercise a single function or cmdlet in isolation. No external state.tests/unit/Pester 5 (PowerShell)
contractAssert the shape of a deployed resource or environment against a fixture. Fixture is authoritative — code conforms to it.tests/contract/Pester 5 + custom fixture schema
integrationExercise module + live dependency (real Azure tenant, real AD, real cluster). Requires a provisioned lab.tests/integration/Pester 5 with lab config
scenarioScripted end-to-end walkthrough of a user journey with pass/fail gates at each step.tests/scenario/PowerShell with structured assertion output
drift-auditAssert a live environment still matches its contract fixture after time passes. Runs on a schedule.tests/drift/Pester 5 + scheduled pipeline

Contract testing ​

Contract tests assert that a deployed environment (Azure resources, cluster configuration, network topology) matches its expected shape as defined in a fixture file. The fixture is the authoritative definition — the deployment must conform to it, not the other way around.

Required for every project that provisions or manages infrastructure.

Fixture files ​

One JSON fixture per logical target. The fixture describes expected state:

tests/contract/
├── fixtures/
│   ├── cluster-01.json         # HCI cluster shape
│   ├── network-topology.json   # VNets, subnets, NSGs
│   └── identity.json           # AD structure, SPNs, managed identities
└── cluster-01.Tests.ps1        # Pester test that loads fixture and asserts conformance

Fixture structure ​

json
{
  "$schema": "./schema/fixture.schema.json",
  "target": "hcs-cluster-01",
  "environment": "prd",
  "assertions": {
    "cluster_nodes": 3,
    "cluster_name": "hcs-cluster-01",
    "domain": "corp.hybridsolutions.cloud",
    "dns_servers": ["10.0.0.10", "10.0.0.11"],
    "ntp_server": "0.pool.ntp.org"
  }
}

Example contract test ​

powershell
Describe 'hcs-cluster-01 contract' {
    BeforeAll {
        $fixture = Get-Content "$PSScriptRoot/fixtures/cluster-01.json" | ConvertFrom-Json
        $actual  = Get-HCSClusterState -ClusterName $fixture.target
    }

    It 'has the expected number of nodes' {
        $actual.NodeCount | Should -Be $fixture.assertions.cluster_nodes
    }

    It 'is joined to the correct domain' {
        $actual.Domain | Should -Be $fixture.assertions.domain
    }

    It 'has correct DNS servers configured' {
        $actual.DnsServers | Should -Be $fixture.assertions.dns_servers
    }
}

Scenario testing ​

Scenario tests are executable walkthroughs of a user journey end-to-end. Each step has an expected outcome. The scenario passes if every step's gate passes.

Required for every project that ships a user-facing workflow — provisioning, migration, deployment, handover.

Scenario file structure ​

tests/scenario/
├── deploy-cluster.scenario.ps1
├── configure-network.scenario.ps1
└── expected/
    ├── deploy-cluster.expected.json
    └── configure-network.expected.json

Scenario naming ​

Scenario filenames describe the user journey, not the implementation:

deploy-cluster.scenario.ps1         # Good — describes what the user does
test-deploy-cluster.ps1             # Bad — generic test name

Example scenario structure ​

powershell
<#
.SYNOPSIS
    Scenario: deploy a new HCI cluster from config.

.DESCRIPTION
    Steps: load config → validate prerequisites → deploy nodes → configure OS → validate cluster.
    Each step has a named gate. Failure stops the scenario and reports which gate failed.
#>

$scenarioName = 'deploy-cluster'
$gates        = @()

# Step 1 — load config
$step = 'load-config'
try {
    $cfg = Get-Content 'config/variables.yml' -Raw | ConvertFrom-Yaml
    $gates += [PSCustomObject]@{ Step = $step; Result = 'PASS'; Detail = "Loaded $($cfg.compute.azure_local.cluster_name)" }
} catch {
    $gates += [PSCustomObject]@{ Step = $step; Result = 'FAIL'; Detail = $_.Exception.Message }
    $gates | ConvertTo-Json | Out-File "tests/scenario/results/$scenarioName.results.json"
    throw "Scenario stopped at gate: $step"
}

# ... additional steps ...

$gates | ConvertTo-Json -Depth 5 | Out-File "tests/scenario/results/$scenarioName.results.json"
Write-Host "Scenario '$scenarioName' completed. Results: tests/scenario/results/$scenarioName.results.json" -ForegroundColor Green

Unit tests ​

All PowerShell modules and shared functions must have unit tests using Pester 5.

tests/unit/
├── Get-HCSVaultSecret.Tests.ps1
├── Resolve-KeyVaultRef.Tests.ps1
└── Test-HCSPrerequisites.Tests.ps1

Minimum requirements:

  • One test file per module function or cmdlet
  • Test the happy path and at least one failure path
  • Tests must run without Azure credentials — mock external calls with Mock
  • Tests must complete in under 5 seconds per file
powershell
Describe 'Resolve-KeyVaultRef' {
    BeforeAll {
        . "$PSScriptRoot/../../scripts/helpers.ps1"
    }

    It 'returns null for non-URI input' {
        Resolve-KeyVaultRef -KvUri 'plain-string' | Should -BeNullOrEmpty
    }

    It 'parses vault name and secret name from URI' {
        Mock -CommandName 'az' -MockWith { return 'mock-secret-value' }
        $result = Resolve-KeyVaultRef -KvUri 'keyvault://kv-hcs-vault-01/my-secret'
        $result | Should -Be 'mock-secret-value'
    }
}

What every repo must have ​

A conforming HCS repo has, at minimum:

  • [ ] tests/ directory at repo root
  • [ ] tests/unit/ with at least one Pester test per shipped module function
  • [ ] tests/contract/fixtures/ with at least one fixture if the repo provisions infrastructure
  • [ ] tests/scenario/ with at least one scenario if the repo ships a user-facing workflow
  • [ ] ADO pipeline stage that runs Invoke-Pester on every PR
  • [ ] Scheduled ADO pipeline that re-runs contract tests against live environments weekly

Repos that are purely documentation are exempt from contract and scenario requirements but must have unit tests for any embedded scripts.


CI enforcement ​

Add a test stage to every ADO pipeline that runs on PRs:

yaml
- stage: Test
  jobs:
    - job: UnitTests
      pool:
        vmImage: windows-latest
      steps:
        - task: PowerShell@2
          displayName: Run Pester tests
          inputs:
            targetType: inline
            script: |
              Install-Module Pester -Force -SkipPublisherCheck
              $results = Invoke-Pester -Path tests/unit/ -PassThru -Output Detailed
              if ($results.FailedCount -gt 0) { exit 1 }

What does not belong in tests/ ​

  • Copies of shared assertion helpers — if two repos need the same helper, extract it into a shared module in this (platform) repo
  • Live credentials or environment-specific connection strings — these live in ADO Variable Groups and are injected by the pipeline
  • Exploratory test scripts not tied to a specific assertion — those belong in scripts/ or scratch/ if they exist at all

Copyright © Hybrid Cloud Solutions LLC — Kristopher Turner