Continuous Testing Strategy in Azure DevOps
How to build a continuous testing strategy in Azure DevOps. Covers test pyramid implementation, pipeline stage design, test selection strategies, environment promotion gates, and quality metrics for continuous delivery.
Continuous testing means quality is checked at every stage of the delivery pipeline — not just at the end. In Azure DevOps, this is implemented through layered pipeline stages, each running an appropriate set of tests before allowing progress to the next stage.
The continuous testing pyramid in Azure DevOps
▲
/E2E\ Stage 4: Staging validation
/─────\ (Playwright, manual smoke)
/ Integ \ Stage 3: Integration tests
/─────────\ (API, service, contract)
/ Unit \ Stage 2: Unit tests
/─────────────\ (Jest, JUnit, pytest)
/ Static Checks \ Stage 1: Lint, types, SAST
───────────────────
Each layer is a stage gate. A failure at any stage stops the pipeline and provides fast feedback to the developer.
Stage 1: Static checks (< 2 minutes)
- stage: StaticChecks
displayName: Static Analysis
jobs:
- job: Checks
steps:
- script: npm run lint
- script: npm run typecheck
- script: npm audit --audit-level=high # Security check
- script: npx license-checker --onlyAllow 'MIT;Apache-2.0;BSD-3-Clause'Stage 2: Unit tests (< 5 minutes)
- stage: UnitTests
dependsOn: StaticChecks
jobs:
- job: Unit
steps:
- script: npm run test:unit -- --coverage --reporter=junit
- task: PublishTestResults@2
inputs:
testResultsFormat: JUnit
testResultsFiles: results/unit.xml
condition: always()
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: coverage/cobertura-coverage.xmlFail the build if coverage drops below threshold (configured in jest.config.js).
Stage 3: Integration tests (< 10 minutes)
- stage: IntegrationTests
dependsOn: UnitTests
jobs:
- job: Integration
services:
postgres: postgres # Service container
redis: redis
steps:
- script: npm run db:migrate
- script: npm run test:integration
- task: PublishTestResults@2
condition: always()Stage 4: Deploy to staging + smoke tests (< 15 minutes)
- stage: Staging
dependsOn: IntegrationTests
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployStaging
environment: staging
strategy:
runOnce:
deploy:
steps:
- script: ./scripts/deploy-staging.sh
- job: SmokeTests
dependsOn: DeployStaging
steps:
- script: npx playwright test --grep @smoke
env:
BASE_URL: $(STAGING_URL)
- task: PublishTestResults@2
condition: always()Stage 5: Full regression + approval gate
- stage: RegressionAndApproval
dependsOn: Staging
jobs:
- job: FullRegression
steps:
- script: npx playwright test --workers=6
env:
BASE_URL: $(STAGING_URL)
- task: PublishTestResults@2
condition: always()
- job: QAApproval
dependsOn: FullRegression
pool: server # Human approval — no agent needed
steps:
- task: ManualValidation@0
inputs:
instructions: |
Full regression: $(RegressionPassed) tests passed.
Review the test report before approving production deployment.
Link: $(System.CollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)
onTimeout: reject
timeout: 1d # 24 hours to approveTest selection strategy
Not all tests should run on every trigger:
| Trigger | Tests to run |
|---|---|
| Developer PR | Unit + lint + smoke (fast) |
| Merge to main | All stages |
| Nightly schedule | Full regression + performance |
| Pre-release | Full regression + manual sign-off |
# Conditional test selection
- script: npx playwright test --grep $(TEST_GREP)
env:
TEST_GREP: ${{ if eq(variables['Build.Reason'], 'PullRequest') }}'@smoke'${{ else }}''${{ end }}Quality gates for release
Before production deployment, verify:
- script: |
PASS_RATE=$(cat test-results/metrics.json | jq '.passRate')
echo "Pass rate: $PASS_RATE%"
if (( $(echo "$PASS_RATE < 95" | bc -l) )); then
echo "##vso[task.logissue type=error]Pass rate $PASS_RATE% is below 95% threshold"
exit 1
fi
displayName: Quality gate checkCommon errors and fixes
Error: Integration tests fail because database isn't ready
Fix: Use service container health checks. The job won't start until the health check passes. Add --health-cmd, --health-interval, --health-retries to the container options.
Error: Manual approval gate expires during long weekends
Fix: Set timeout to at least 72 hours for weekends. Also configure notifications so approvers are alerted immediately when the gate opens.
Error: Regression stage runs even when staging deployment failed
Fix: Use dependsOn: DeployStaging with condition: succeeded() on the regression job. Without the condition, the job may still run when the deployment job fails.
Stay ahead in AI-driven QA
Get practical tutorials on test automation, AI testing, and quality engineering — straight to your inbox. No spam, unsubscribe any time.
Discussion
Sign in with GitHub to comment · powered by Giscus