Why Structured Testing Matters
In Modules 4–7, you built pipelines that compile, test, and deploy code automatically. But here's the gap those automated pipelines can't fill alone: how do you know the application actually works from a user's perspective? How do you prove to auditors that every requirement was tested? How do you capture the bugs that automated tests never anticipated?
Azure Test Plans bridges this gap — it provides structured test management that complements your automated test suites with manual verification, exploratory discovery, and complete traceability.
Manual vs Automated Testing — Both Have a Place
| Testing Type | Best For | Weakness |
|---|---|---|
| Automated (Unit/Integration) | Regression, logic validation, fast feedback loops | Can't judge UX, aesthetics, or "feel" |
| Manual (Scripted) | Acceptance criteria verification, compliance evidence | Slow, repetitive, human error |
| Exploratory (Unscripted) | Finding unexpected bugs, UX issues, edge cases | Not repeatable, depends on tester skill |
The Traceability Chain
The ultimate goal of structured testing is traceability — the ability to answer "was this requirement tested, and did it pass?" for any requirement at any time:
flowchart LR
A[User Story] --> B[Test Case]
B --> C[Test Run]
C --> D{Pass?}
D -->|Yes| E[Verified ✓]
D -->|No| F[Bug Created]
F --> G[Fix Committed]
G --> H[Re-test]
H --> E
This chain is critical in regulated industries (healthcare, finance, government) where you must demonstrate that every requirement has been verified. But even for startups, traceability means fewer "I thought someone tested that" incidents.
For testing theory and methodology fundamentals, see Part 18: Testing Fundamentals.
Test Plans, Suites & Cases
Azure Test Plans organizes testing into a three-level hierarchy. Understanding this hierarchy is essential before you create anything:
The Hierarchy
| Level | What It Is | Example |
|---|---|---|
| Test Plan | Top-level container for a release, sprint, or milestone | "Sprint 14 Test Plan" or "v2.5 Release Plan" |
| Test Suite | Logical grouping of related test cases within a plan | "Login Module Tests" or "Payment Flow Tests" |
| Test Case | Individual test with steps, expected results, and parameters | "Verify login with valid credentials" |
Test Suite Types
Azure DevOps offers three types of test suites, each serving a different purpose:
- Static Suite — Manually curated collection of test cases. You drag-and-drop cases into the suite. Best for ad-hoc groupings or regression packs.
- Requirement-based Suite — Automatically linked to a work item (User Story, Feature). Every test case in the suite traces back to that requirement. This is the most powerful type for traceability.
- Query-based Suite — Populated dynamically by a work item query. For example, "all test cases tagged 'smoke-test'" or "all test cases modified this sprint." Great for creating a nightly regression suite that auto-updates.
flowchart TD
TP[Test Plan: Sprint 14] --> RS1[Requirement Suite: US-101 Login]
TP --> RS2[Requirement Suite: US-102 Payment]
TP --> SS[Static Suite: Regression Pack]
TP --> QS[Query Suite: Smoke Tests]
RS1 --> TC1[TC: Valid login]
RS1 --> TC2[TC: Invalid password]
RS1 --> TC3[TC: Account locked]
RS2 --> TC4[TC: Credit card payment]
RS2 --> TC5[TC: PayPal checkout]
SS --> TC6[TC: Homepage loads]
SS --> TC1
QS --> TC6
QS --> TC1
Test Case Anatomy
A well-written test case has clear steps, expected results, and optional parameters for data-driven testing:
# Test Case: Verify login with valid credentials (TC-001)
# This is a conceptual representation — you create these in the Azure DevOps UI
Title: Verify login with valid credentials
Area Path: MyProject\Login Module
Priority: 1 (Critical)
State: Ready
Steps:
1. Navigate to https://myapp.com/login
Expected: Login page displays with email and password fields
2. Enter valid email address
Expected: Email field accepts input without error
3. Enter valid password
Expected: Password field masks input
4. Click "Sign In" button
Expected: User is redirected to dashboard within 3 seconds
5. Verify user name appears in top-right corner
Expected: "Welcome, [User Name]" text is displayed
# Parameters (for data-driven execution):
Parameters:
- @email: admin@contoso.com, user@contoso.com, new.user@contoso.com
- @password: P@ssw0rd123, UserPass!456, NewUser789!
- @expectedName: Admin User, Standard User, New User
# Shared Steps reference (reusable across test cases):
Shared Step "Navigate to Login":
1. Open browser
2. Navigate to $(BaseUrl)/login
3. Wait for page to fully load
Configurations
Configurations let you run the same test case across multiple OS/browser/device combinations without duplicating test cases:
| Configuration Variable | Values |
|---|---|
| Operating System | Windows 11, macOS Sonoma, Ubuntu 22.04 |
| Browser | Chrome, Firefox, Edge, Safari |
| Device Type | Desktop, Tablet, Mobile |
A single test case with 3 OS × 4 browser configurations generates 12 test points — 12 unique combinations to execute and track independently.
Manual Test Execution
Once your test plan structure is in place, it's time to execute. Azure DevOps provides two runners for manual test execution:
- Web Runner — Browser-based, works everywhere, no installation needed
- Desktop Runner — Windows application with richer capture capabilities (video recording, advanced annotations)
Test Outcomes
Each test step (and the overall test case) receives one of four outcomes:
| Outcome | Meaning | When to Use |
|---|---|---|
| Passed ✅ | Step/case meets expected result | Application behaves as specified |
| Failed ❌ | Actual result differs from expected | Bug found — creates a linked bug work item |
| Blocked 🚫 | Cannot execute due to external dependency | Environment down, prerequisite feature not deployed |
| Not Applicable ⊘ | Test no longer relevant for this configuration | Feature removed, test obsolete for this platform |
Creating Bugs from Failed Steps
The killer feature of the test runner: when a step fails, you click "Create bug" and Azure DevOps automatically populates:
- Repro steps — All steps executed so far (with pass/fail status)
- System info — Browser, OS, screen resolution
- Attachments — Screenshots captured during execution
- Links — Back to the test case, test run, and user story
This means developers receive perfectly documented bugs with exact reproduction steps — no more "it doesn't work" tickets with zero context.
Managing Test Runs with Azure CLI
# List test plans in a project
az devops invoke \
--area testplan \
--resource plans \
--org https://dev.azure.com/myorg \
--project MyProject \
--http-method GET
# Query test results for a specific test run
az devops invoke \
--area test \
--resource runs \
--route-parameters runId=12345 \
--org https://dev.azure.com/myorg \
--project MyProject \
--http-method GET
# Get test points (which test cases to run in which configuration)
az devops invoke \
--area testplan \
--resource points \
--route-parameters planId=100 suiteId=200 \
--org https://dev.azure.com/myorg \
--project MyProject \
--http-method GET
Test Progress Charts
Azure Test Plans provides built-in charts showing test progress over time — how many test cases are passing, failing, or not yet run. Add these to your dashboard to give stakeholders visibility into testing progress without needing Test Plans access.
Exploratory Testing
Scripted tests verify what you expect. Exploratory testing discovers what you didn't expect. It's session-based, unscripted testing where a skilled tester explores the application with a mission but without predetermined steps.
Test & Feedback Extension
The Azure Test & Feedback browser extension (Chrome/Edge) is the primary tool for exploratory testing. It captures everything you do during a session:
- Screenshots — Annotated with drawing tools and text
- Screen recordings — Video of your exploration session
- Notes — Free-text observations as you explore
- Bugs — Created directly with captured evidence auto-attached
- Tasks — Follow-up actions ("investigate this edge case further")
- Timeline — Automatic log of pages visited and actions taken
Session Management
A well-structured exploratory session has three elements:
- Charter — "Explore the checkout flow focusing on error handling when payment fails"
- Timebox — 30–90 minutes (prevents fatigue and scope creep)
- Stakeholder — Link the session to a Feature or User Story for traceability
When to Use Exploratory vs Scripted
| Scenario | Use Exploratory | Use Scripted |
|---|---|---|
| New feature just deployed | ✅ Find unexpected issues early | ✅ Verify acceptance criteria |
| Regression after major refactor | ✅ Explore areas not covered by automation | ✅ Run existing regression suite |
| Compliance audit evidence | ❌ Not repeatable enough for auditors | ✅ Documented steps with pass/fail |
| Usability evaluation | ✅ Human judgment essential | ❌ Can't script "feels confusing" |
| Performance under load | ❌ Need automated load tools | ✅ Automated load/stress tests |
Test Automation Integration
Azure Test Plans doesn't replace automated testing — it integrates with it. Your pipelines run automated tests (unit, integration, E2E) and publish results back to Azure DevOps, creating a unified view of both manual and automated test health.
Publishing Test Results from Pipelines
# Publishing test results from different frameworks
# Each framework produces results in a specific format
# .NET — produces TRX format (VSTest)
steps:
- script: dotnet test --logger trx --results-directory $(Agent.TempDirectory)
displayName: 'Run .NET tests'
- task: PublishTestResults@2
displayName: 'Publish .NET test results'
inputs:
testResultsFormat: 'VSTest' # Format: VSTest, JUnit, NUnit, xUnit, CTest
testResultsFiles: '**/*.trx' # Glob pattern to find result files
searchFolder: '$(Agent.TempDirectory)'
mergeTestResults: true # Merge results from multiple files
testRunTitle: 'Unit Tests (.NET)' # Display name in Test Runs tab
condition: always() # Publish even if tests fail
# Java/Maven — produces JUnit XML format
- script: mvn test
displayName: 'Run Java tests'
- task: PublishTestResults@2
displayName: 'Publish Java test results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/surefire-reports/TEST-*.xml'
testRunTitle: 'Unit Tests (Java)'
condition: always()
# JavaScript/Node — Jest with JUnit reporter
- script: npx jest --ci --reporters=default --reporters=jest-junit
displayName: 'Run Jest tests'
env:
JEST_JUNIT_OUTPUT_DIR: '$(Agent.TempDirectory)/jest-results'
- task: PublishTestResults@2
displayName: 'Publish Jest test results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '$(Agent.TempDirectory)/jest-results/*.xml'
testRunTitle: 'Unit Tests (Jest)'
condition: always()
# Python — pytest with JUnit XML output
- script: pytest --junitxml=$(Agent.TempDirectory)/pytest-results.xml
displayName: 'Run Python tests'
- task: PublishTestResults@2
displayName: 'Publish Python test results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '$(Agent.TempDirectory)/pytest-results.xml'
testRunTitle: 'Unit Tests (Python)'
condition: always()
Linking Automated Tests to Test Cases
You can link an automated test method to a manual test case in Azure Test Plans. This means:
- The test case shows "Automated" status (no manual execution needed)
- Pipeline test results automatically update the test case outcome
- Requirements traceability works seamlessly for automated tests too
To link: open the test case → Associated Automation tab → select the automated test method by its fully-qualified name (e.g., MyProject.Tests.LoginTests.ValidLogin_ShouldRedirectToDashboard).
Flaky Test Detection
Azure DevOps automatically detects flaky tests — tests that pass and fail intermittently on the same code. When a test is marked flaky:
- It's flagged in the test results with a ⚠️ icon
- Pipeline pass/fail decisions can optionally exclude flaky test failures
- A "Flaky Tests" report shows which tests need stabilization
Enable flaky test detection in Project Settings → Pipelines → Test Management → Flaky test detection.
Code Coverage & Quality Gates
Code coverage measures what percentage of your source code is exercised by automated tests. It's not a measure of test quality, but it's a useful indicator of test completeness.
# Publishing code coverage from different frameworks
# Coverage data is displayed on the pipeline run summary
# .NET — Cobertura format via coverlet
steps:
- script: |
dotnet test \
--collect:"XPlat Code Coverage" \
--results-directory $(Agent.TempDirectory)/coverage
displayName: 'Run tests with coverage'
- task: PublishCodeCoverageResults@2
displayName: 'Publish code coverage'
inputs:
summaryFileLocation: '$(Agent.TempDirectory)/coverage/**/coverage.cobertura.xml'
# Supported formats: Cobertura, JaCoCo
# Cobertura is the most widely supported (C#, Python, JS, Go)
# Java — JaCoCo format via Maven
- script: mvn verify # JaCoCo plugin runs during verify phase
displayName: 'Run tests with JaCoCo coverage'
- task: PublishCodeCoverageResults@2
displayName: 'Publish Java coverage'
inputs:
summaryFileLocation: '**/site/jacoco/jacoco.xml'
# Python — Cobertura via pytest-cov
- script: |
pytest \
--cov=src \
--cov-report=xml:$(Agent.TempDirectory)/coverage.xml \
--cov-report=html:$(Agent.TempDirectory)/htmlcov
displayName: 'Run Python tests with coverage'
- task: PublishCodeCoverageResults@2
displayName: 'Publish Python coverage'
inputs:
summaryFileLocation: '$(Agent.TempDirectory)/coverage.xml'
Setting Coverage Thresholds as Quality Gates
# Pipeline that fails if code coverage drops below threshold
# This prevents merging PRs that reduce overall coverage
trigger:
branches:
include: [main]
pool:
vmImage: 'ubuntu-latest'
steps:
- script: dotnet test --collect:"XPlat Code Coverage" --results-directory $(Agent.TempDirectory)/coverage
displayName: 'Run tests with coverage'
- task: PublishCodeCoverageResults@2
inputs:
summaryFileLocation: '$(Agent.TempDirectory)/coverage/**/coverage.cobertura.xml'
# Parse coverage and enforce threshold
- script: |
# Extract line coverage percentage from Cobertura XML
COVERAGE=$(python3 -c "
import xml.etree.ElementTree as ET
tree = ET.parse('$(Agent.TempDirectory)/coverage/*/coverage.cobertura.xml')
root = tree.getroot()
line_rate = float(root.attrib['line-rate']) * 100
print(f'{line_rate:.1f}')
")
echo "Code coverage: ${COVERAGE}%"
echo "##vso[task.setvariable variable=codeCoverage]${COVERAGE}"
# Fail if below threshold
THRESHOLD=80
if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then
echo "##vso[task.logissue type=error]Coverage ${COVERAGE}% is below threshold ${THRESHOLD}%"
echo "##vso[task.complete result=Failed;]Coverage gate failed"
exit 1
fi
echo "✅ Coverage ${COVERAGE}% meets threshold ${THRESHOLD}%"
displayName: 'Enforce coverage threshold (80%)'
Branch Policy Integration
For maximum enforcement, combine coverage thresholds with branch policies (covered in Module 3):
- Create a build validation policy on your
mainbranch - The validation pipeline includes the coverage threshold check above
- PRs that reduce coverage below the threshold cannot be merged
This creates a ratchet effect — coverage can only go up, never down.
Quality Gates in Pipelines
Quality gates are automated checkpoints that prevent bad code from progressing through your pipeline. They transform your pipeline from "build and deploy" into "build, prove quality, then deploy."
Types of Quality Gates
| Gate Type | What It Checks | Typical Threshold |
|---|---|---|
| Test pass rate | Percentage of automated tests that pass | 100% (no failures allowed) |
| Code coverage | Lines/branches exercised by tests | ≥ 80% line coverage |
| Static analysis | Code smells, bugs, vulnerabilities | Zero critical/blocker issues |
| Security scan | Known vulnerabilities in dependencies | Zero high/critical CVEs |
| Performance | Response times, throughput benchmarks | P95 latency < 200ms |
Complete Pipeline with Quality Gates
# Production-grade pipeline with multiple quality gates
# Each gate must pass before the next stage proceeds
trigger:
branches:
include: [main]
variables:
- name: minCoverage
value: 80
- name: maxCriticalIssues
value: 0
stages:
# ─── STAGE 1: BUILD & UNIT TEST ─────────────────────────
- stage: Build
displayName: 'Build & Unit Test'
jobs:
- job: BuildAndTest
pool:
vmImage: 'ubuntu-latest'
steps:
- script: dotnet build --configuration Release
displayName: 'Build'
- script: |
dotnet test --configuration Release \
--logger trx \
--collect:"XPlat Code Coverage" \
--results-directory $(Agent.TempDirectory)/results
displayName: 'Run unit tests with coverage'
- task: PublishTestResults@2
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/*.trx'
searchFolder: '$(Agent.TempDirectory)/results'
condition: always()
- task: PublishCodeCoverageResults@2
inputs:
summaryFileLocation: '$(Agent.TempDirectory)/results/**/coverage.cobertura.xml'
# QUALITY GATE: Coverage threshold
- script: |
COVERAGE=$(python3 -c "
import xml.etree.ElementTree as ET, glob
files = glob.glob('$(Agent.TempDirectory)/results/**/coverage.cobertura.xml', recursive=True)
tree = ET.parse(files[0])
print(f\"{float(tree.getroot().attrib['line-rate'])*100:.1f}\")
")
echo "Coverage: ${COVERAGE}%"
if (( $(echo "$COVERAGE < $(minCoverage)" | bc -l) )); then
echo "##vso[task.logissue type=error]Coverage ${COVERAGE}% < $(minCoverage)%"
exit 1
fi
displayName: 'Gate: Coverage ≥ $(minCoverage)%'
# ─── STAGE 2: STATIC ANALYSIS (SonarCloud) ──────────────
- stage: StaticAnalysis
displayName: 'Static Analysis'
dependsOn: Build
jobs:
- job: SonarScan
pool:
vmImage: 'ubuntu-latest'
steps:
- task: SonarCloudPrepare@2
inputs:
SonarCloud: 'SonarCloud-Connection'
organization: 'myorg'
scannerMode: 'dotnet'
projectKey: 'myorg_myproject'
extraProperties: |
sonar.cs.opencover.reportsPaths=$(Agent.TempDirectory)/results/**/coverage.cobertura.xml
- script: dotnet build
displayName: 'Build for analysis'
- task: SonarCloudAnalyze@2
displayName: 'Run SonarCloud analysis'
# QUALITY GATE: SonarCloud quality gate status
- task: SonarCloudPublish@2
displayName: 'Publish SonarCloud results'
- script: |
# Check SonarCloud quality gate via API
STATUS=$(curl -s -u "$SONAR_TOKEN:" \
"https://sonarcloud.io/api/qualitygates/project_status?projectKey=myorg_myproject" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['projectStatus']['status'])")
echo "SonarCloud Quality Gate: $STATUS"
if [ "$STATUS" != "OK" ]; then
echo "##vso[task.logissue type=error]SonarCloud quality gate FAILED"
exit 1
fi
displayName: 'Gate: SonarCloud quality gate'
env:
SONAR_TOKEN: $(SonarToken)
# ─── STAGE 3: DEPLOY (only if all gates pass) ───────────
- stage: Deploy
displayName: 'Deploy to Staging'
dependsOn: [Build, StaticAnalysis]
condition: succeeded()
jobs:
- deployment: deployApp
pool:
vmImage: 'ubuntu-latest'
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- script: echo "All quality gates passed — deploying!"
displayName: 'Deploy application'
End-to-End Traceability
Traceability is the thread that connects every piece of your delivery pipeline — from the initial requirement through development, testing, building, and deploying. Azure DevOps provides this traceability automatically when you link work items, test cases, commits, and pipelines correctly.
flowchart TD
F[Feature: User Authentication] --> US[User Story: Login with Email]
US --> TC[Test Case: Verify login]
US --> C[Commit: Implement login API]
C --> PR["Pull Request #142"]
PR --> BV[Build Validation Pipeline]
BV --> TR[Test Run: 45 passed, 0 failed]
TC --> TR
PR --> B["Build #891"]
B --> D[Deploy to Staging]
D --> MT[Manual Test Run: 12 passed]
MT --> P[Deploy to Production]
Requirements Coverage Report
The Requirements Quality widget (available on dashboards) shows how many of your requirements have associated test cases, and of those, how many are passing:
- Requirements with tests — How many user stories have linked test cases
- Requirements passing — Of those with tests, how many have all tests passing
- Requirements failing — User stories where at least one test is failing
- Requirements not tested — Stories with zero linked tests (the dangerous gaps)
Test Gap Analysis
A test gap is a requirement with no associated test cases. Azure DevOps makes these visible through:
- Requirement-based suites — Empty suites highlight untested stories
- Board annotations — Work items show test status directly on the board
- Queries — "User stories with zero test cases" query to find gaps
Compliance Evidence for Audits
For regulated industries, traceability isn't optional — it's a legal requirement. Azure DevOps provides audit-ready evidence:
- Test run results — Timestamped records of who ran which tests, when, with what outcome
- Change history — Every modification to test cases is logged
- Deployment history — Which build deployed to production, linked to which test runs
- Export capabilities — Export test plans/results to PDF/Excel for external auditors
Inline Tests & Quick Testing
Not every team needs the full Azure Test Plans experience. For teams on the Basic plan (without Test Plans license), Azure DevOps offers inline tests — lightweight test tracking directly on the board.
Creating Inline Tests from User Stories
On the Kanban board, hover over a User Story card and click "+ Add Test". This creates a simple test case linked to that story — no Test Plans license required. You can mark tests as passed/failed directly from the board.
When Inline Tests Are Sufficient
| Feature | Inline Tests (Free) | Full Test Plans (Licensed) |
|---|---|---|
| Create test cases | ✅ Simple title + pass/fail | ✅ Full steps, parameters, shared steps |
| Link to user stories | ✅ Automatic | ✅ Automatic (requirement suites) |
| Test runner | ❌ No execution runner | ✅ Web + Desktop runner with capture |
| Exploratory testing | ❌ Not available | ✅ Full session management |
| Configurations | ❌ Not supported | ✅ OS/browser matrix |
| Compliance/audit | ❌ Insufficient evidence | ✅ Full audit trail |
| Best for | Small teams, internal tools | Regulated industries, enterprise QA |
Recommendation: Start with inline tests. When you find yourself needing test runners, exploratory sessions, or compliance reports, upgrade to the full Test Plans license. The upgrade is additive — your inline tests remain linked.
Case Study: Regulated Industry Testing
HIPAA-Compliant Test Management for a Patient Portal
Challenge: A healthcare SaaS company building a patient portal needed to demonstrate to auditors that every HIPAA-related requirement was tested, with evidence of who tested what and when. Their previous approach — spreadsheets and email threads — failed their last compliance audit.
Solution: Azure Test Plans with Full Traceability
Test Plan Structure:
- One test plan per release (quarterly releases)
- Requirement-based suites for every HIPAA user story (214 stories)
- Each user story has 3–8 test cases covering positive, negative, and boundary scenarios
- Configurations: Chrome/Edge × Windows/macOS (4 combinations per test)
Testing Strategy (Layered):
- Automated (80%): Unit + integration tests run in pipeline, results published via
PublishTestResults@2, linked to test cases - Manual scripted (15%): Complex workflows (patient data flows, consent management) executed with the test runner
- Exploratory (5%): Security-focused sessions — testers try to access unauthorized data, bypass access controls
Quality Gates in Pipeline:
- 100% unit test pass rate (zero tolerance)
- 85% code coverage minimum (enforced in PR validation)
- SonarCloud quality gate: zero critical vulnerabilities
- OWASP dependency check: zero high-severity CVEs
Audit Evidence Package (auto-generated):
- Requirements coverage matrix (exported from Test Plans)
- Test run history with timestamps and tester identity
- Deployment audit trail from pipeline (build → staging → production)
- Bug resolution evidence (bug → fix → re-test → pass)
Results
- Audit outcome: Passed HIPAA compliance audit with zero findings (previously had 3 critical findings)
- Requirement coverage: 100% of HIPAA stories have linked, passing test cases
- Audit prep time: 2 weeks → 2 hours (reports are always current)
- Bug escape rate: 12 production bugs/quarter → 2 production bugs/quarter
- Regression confidence: Team deploys weekly instead of quarterly (faster feedback from automated gates)
Exercises
Create a Test Plan with Requirement-Based Suites
Goal: Build a test plan linked to user stories for complete requirement traceability.
- Create 3 user stories in your backlog: "Login with email", "Reset password", "Update profile".
- Create a new Test Plan named "Sprint 1 Test Plan".
- Add a requirement-based suite for each user story (3 suites total).
- Write 2–3 test cases per suite with clear steps and expected results.
- Add a configuration for "Chrome + Windows" and "Edge + macOS".
- Verify the Requirements Quality widget shows all 3 stories linked to tests.
Success criteria: Each user story has a requirement-based suite with test cases. The traceability matrix shows 100% requirement coverage.
Execute Manual Tests and Capture a Bug
Goal: Run manual test cases using the web runner and create a bug from a failed step.
- Deploy a simple web application with a known bug (e.g., form validation that accepts invalid email).
- Open your test plan and select a test case to run.
- Use the Web Runner to execute each step, marking pass or fail.
- When you hit the buggy step, mark it as failed and click "Create Bug".
- Verify the bug is auto-populated with repro steps, system info, and a link back to the test case.
- Fix the bug, then re-run the test case and mark it as passed.
Success criteria: A bug work item exists with auto-populated repro steps from the test runner, linked to both the test case and the user story.
Configure Pipeline Quality Gates
Goal: Build a pipeline with enforced quality gates — 80% code coverage and 100% test pass rate.
- Create a project with unit tests (any language) achieving approximately 85% coverage.
- Write a pipeline that runs tests and publishes both test results and code coverage.
- Add a coverage threshold check (80% minimum) that fails the pipeline if coverage drops.
- Create a branch policy on
mainrequiring this pipeline to pass. - Submit a PR that removes tests (dropping coverage below 80%) and verify it's blocked.
- Fix the PR (restore tests) and verify it merges successfully.
Success criteria: PRs that reduce coverage below 80% are automatically blocked. PRs that maintain coverage merge normally.
Exploratory Testing Session
Goal: Conduct a structured exploratory testing session using the Test & Feedback extension.
- Install the "Azure Test & Feedback" extension in Chrome or Edge.
- Connect it to your Azure DevOps project.
- Start an exploratory session with charter: "Explore the signup flow, focusing on error handling and edge cases."
- Set a 30-minute timebox.
- During the session, capture at least 3 observations: 1 bug, 1 task (follow-up needed), and 1 note (interesting behavior).
- End the session and verify all captured items appear as work items linked to the session.
Success criteria: An exploratory testing session exists with 3+ captured observations (bug, task, note) — each with screenshots and linked to the correct feature area.
Next in the Bootcamp
In Module 9: Artifacts & Package Management, we'll cover Azure Artifacts feeds, publishing and consuming NuGet/npm/Maven/Python packages, upstream sources, feed permissions, and integrating package versioning with your CI/CD pipelines.