3b: Design and Implement a Testing Strategy for Pipelines
Quality and Release Gates
What Are Quality Gates?
Quality gates are automated checkpoints in a pipeline that enforce minimum standards before code progresses to the next stage.
1
2
3
4
5
6
7
Build ──► [Quality Gate] ──► Staging ──► [Release Gate] ──► Production
│ │
Must Pass: Must Pass:
- Unit tests - Integration tests
- Code coverage ≥ 80% - Load test baseline
- No critical vulnerabilities - Manual approval
- Static analysis - Monitoring health
Azure Pipelines — Gates and Checks
Pre-deployment gates (Classic pipelines): | Gate Type | Description | |———–|————-| | Azure Monitor Alerts | Check no active alerts in a window | | Invoke REST API | Call custom API endpoint, validate response | | Query Azure Monitor | Check specific metric thresholds | | Query Work Items | Ensure no open critical bugs | | Security and compliance assessment | Microsoft Defender for Cloud |
YAML-based checks (Environments):
1
2
3
4
5
6
7
8
9
10
# Configure in Environments UI, then reference:
- stage: DeployProd
jobs:
- deployment: Deploy
environment: 'production' # ← Gates/approvals configured on this environment
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to production"
In Environment settings, configure:
- Required reviewers (manual approval)
- Branch control (only deploy from
main) - Business hours check
- Invoke REST API gate
- Query Azure Monitor alerts
GitHub Actions — Environments and Protection Rules
1
2
3
4
5
6
7
8
9
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://myapp.example.com
steps:
- name: Deploy
run: ./deploy.sh
Environment protection rules (configured in GitHub UI):
- Required reviewers: Named users/teams must approve
- Wait timer: Delay before deployment (e.g., 10 minutes)
- Deployment branches: Only from
mainorrelease/*
Comprehensive Testing Strategy
Testing Pyramid
1
2
3
4
5
6
7
8
9
10
/\
/ \
/ UI \ ← Few, slow, expensive
/______\
/ \
/ Integration\ ← Some
/______________\
/ \
/ Unit Tests \ ← Many, fast, cheap
/____________________\
| Test Type | Scope | Speed | Cost | Run In |
|---|---|---|---|---|
| Unit | Single function/class | Very fast (<1s) | Low | Every build |
| Integration | Multiple components | Moderate | Medium | Every build/PR |
| End-to-End (E2E) | Full user workflow | Slow | High | Nightly/pre-release |
| Load/Performance | System under stress | Very slow | High | Pre-release |
| Contract | API consumer/provider | Fast | Low | Every build |
| Security/SAST | Source code | Moderate | Low | Every build |
| DAST | Running application | Slow | Medium | Staging |
Unit Tests
Characteristics:
- Test a single unit (function, method, class) in isolation
- Use mocks/stubs for dependencies
- Must be fast (milliseconds per test)
- Must be deterministic (same input → same output, always)
- No network, file system, or database access
Example (C# / xUnit):
1
2
3
4
5
6
7
8
9
10
11
12
13
[Fact]
public void CalculateTotal_WithDiscount_ReturnsCorrectAmount()
{
// Arrange
var calculator = new OrderCalculator();
var order = new Order { Items = new[] { new Item { Price = 100m } } };
// Act
var total = calculator.CalculateTotal(order, discountPercent: 10);
// Assert
Assert.Equal(90m, total);
}
Pipeline (Azure Pipelines):
1
2
3
4
5
6
7
- task: DotNetCoreCLI@2
displayName: 'Run Unit Tests'
inputs:
command: 'test'
projects: '**/*Tests/*.csproj'
arguments: '--configuration Release --collect:"XPlat Code Coverage"'
publishTestResults: true
Integration Tests
Characteristics:
- Test multiple components working together
- May use test databases, in-memory services, or Docker containers
- Slower than unit tests
- Reveal interface/contract issues between components
Service containers in Azure Pipelines:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
jobs:
- job: IntegrationTests
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
ports:
- 5432:5432
steps:
- script: dotnet test --filter Category=Integration
env:
DB_CONNECTION: "Host=localhost;Database=testdb;Username=testuser;Password=testpass"
GitHub Actions service containers:
1
2
3
4
5
6
7
8
9
10
services:
redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
Load Tests
Azure Load Testing:
- Cloud-based, managed load testing service
- Based on Apache JMeter scripts or URL-based tests
- Can be integrated into CI/CD pipelines
Pipeline integration:
1
2
3
4
5
6
7
8
9
- task: AzureLoadTest@1
inputs:
azureSubscription: 'MyServiceConnection'
loadTestConfigFile: 'load-test.yaml'
resourceGroup: 'my-rg'
loadTestResource: 'my-load-test'
failCriteria: |
avg(response_time_ms) > 2000
percentage(error) > 5
Load test types: | Type | Pattern | Goal | |——|———|——| | Load test | Constant load | Verify behaviour under expected load | | Stress test | Increasing load until failure | Find breaking point | | Spike test | Sudden burst | Test response to unexpected traffic | | Endurance/Soak test | Sustained load over time | Detect memory leaks |
Implementing Tests in a Pipeline
Configuring Test Tasks
Azure Pipelines test tasks:
| Task | Package Type |
|---|---|
DotNetCoreCLI@2 |
.NET (xUnit, NUnit, MSTest) |
VSTest@2 |
Visual Studio test runner |
Maven@3 |
Maven (JUnit) |
Gradle@3 |
Gradle (JUnit) |
Npm@1 |
Node.js (Jest, Mocha) |
PublishTestResults@2 |
Publish any test results (JUnit XML format) |
Publishing test results (generic):
1
2
3
4
5
6
7
- task: PublishTestResults@2
condition: succeededOrFailed() # ← Always publish, even if tests fail
inputs:
testResultsFormat: 'JUnit' # JUnit, NUnit, VSTest, XUnit, CTest
testResultsFiles: '**/test-results/*.xml'
mergeTestResults: true
testRunTitle: 'Unit Tests'
Configuring Test Agents / Runners
Azure DevOps Agent pools:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
pool:
name: 'Default' # Self-hosted pool
# OR
vmImage: 'ubuntu-latest' # Microsoft-hosted
# Matrix strategy for multi-platform testing:
strategy:
matrix:
linux:
pool.vmImage: 'ubuntu-latest'
windows:
pool.vmImage: 'windows-latest'
mac:
pool.vmImage: 'macOS-latest'
Parallel test execution:
1
2
3
4
- task: DotNetCoreCLI@2
inputs:
command: 'test'
arguments: '--parallel' # Run test projects in parallel
Test impact analysis (Azure DevOps):
- Runs only tests affected by code changes
- Reduces test execution time significantly
- Enable in VSTest task:
runOnlyImpactedTests: true
Code Coverage Analysis
Collecting Coverage
Azure Pipelines (.NET):
1
2
3
4
5
6
7
8
9
- task: DotNetCoreCLI@2
inputs:
command: 'test'
arguments: '--collect:"XPlat Code Coverage" --results-directory $(Agent.TempDirectory)'
- task: PublishCodeCoverageResults@2
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
GitHub Actions (.NET with Coverlet):
1
2
3
4
5
6
7
8
9
- name: Test with coverage
run: dotnet test --collect:"XPlat Code Coverage"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: $
files: '**/coverage.cobertura.xml'
fail_ci_if_error: true
Enforcing Coverage Thresholds
.NET — enforce in test command:
1
2
3
4
5
dotnet test /p:CollectCoverage=true \
/p:CoverletOutputFormat=cobertura \
/p:Threshold=80 \
/p:ThresholdType=line \
/p:ThresholdStat=minimum
SonarQube/SonarCloud quality gate:
- Configure coverage threshold in Quality Gate settings
- Add
SonarQubePrepare,SonarQubeAnalyze,SonarQubePublishtasks - Pipeline fails if quality gate not met
Coverage types: | Type | Measures | |——|———| | Line coverage | % of code lines executed | | Branch coverage | % of decision branches taken (if/else, switch) | | Function coverage | % of functions called | | Statement coverage | % of statements executed |
⭐ Branch coverage is more meaningful than line coverage for complex logic.
🧠 Key Exam Tips for Testing Strategy
| Scenario | Answer |
|---|---|
| Block deployment if code coverage drops below 80% | Configure coverage threshold + quality gate in SonarQube or dotnet test /p:Threshold=80 |
| Run tests on multiple OS versions in one pipeline | Matrix strategy |
| Only run tests affected by the code change | Test Impact Analysis (Azure DevOps VSTest task) |
| Verify production is healthy before completing deployment | Post-deployment gate (query Azure Monitor alerts) |
| Test a database integration in pipeline without external DB | Service containers (Docker) in pipeline job |
| Detect performance regression | Azure Load Testing integrated into pipeline with failCriteria |
| Prevent merge if pipeline tests fail | Branch policy: Build validation |
| Store test results for reporting | PublishTestResults@2 with condition: succeededOrFailed() |