Test Management and Traceability

This guide demonstrates how to integrate test reports into your documentation with full traceability between test specifications, test cases, source code, and test results.

Overview

A comprehensive testing approach includes:

  • Test Specifications: Define what needs to be tested

  • Test Cases: Actual test implementations in code

  • Source Code: The implementation being tested

  • Traceability: Links between all these elements

  • Test Reports: Results from test execution

By using Sphinx-Needs for test specifications, sphinx-codelinks for code tracing, and sphinx-test-reports for test results, we create a complete traceable testing ecosystem.

Workflow

        graph LR
   REQ[Requirements] --> TS[Test Specifications]
   TS --> TC[Test Cases in Code]
   TC --> IMPL[Implementation Code]
   TC --> RUN[Test Execution]
   RUN --> REPORT[Test Reports]
   REPORT --> DOC[Documentation]

   style REQ fill:#FFB300
   style TS fill:#A6BDD7
   style TC fill:#A6BDD7
   style IMPL fill:#fa8638
   style REPORT fill:#4aac73
   style DOC fill:#F6768E
    

Step 1: Define Test Specifications

Create test specifications using the test-spec directive to define what needs to be tested:

Test Specification: Factorial Function Test Specification TS_FACTORIAL
status: open
tags: factorial, math
links incoming: T_FACT_001, T_FACT_002, T_FACT_003

Test the factorial function for:

  • Negative numbers (should return 1)

  • Zero (should return 1)

  • Positive numbers (should return correct factorial)

The factorial function is critical for mathematical operations and must handle edge cases correctly.

Test Specification: Prime Number Check Test Specification TS_PRIME
status: open
tags: prime, math

Test the IsPrime function for:

  • Negative numbers (should return false)

  • Trivial cases (0, 1, 2, 3)

  • Positive numbers (both prime and composite)

Prime number detection is used in cryptographic operations and must be accurate.

Step 2: Annotate Test Cases in Code

Add sphinx-codelinks annotations to your test cases. The annotation format is e.g.:

// @<description>, <need_id>, <need_type>
TEST(TestSuite, TestName) {
    // test implementation
}

Example from sample1_unittest.cpp:

Annotated test case with GTest properties
// @Test negative factorial values, T_FACT_001, test, [TS_FACTORIAL, IMPL_2]
TEST(FactorialTest, Negative) {
  // Link this test to documentation via GTest property
  RecordProperty("need_id", "T_FACT_001");
  RecordProperty("requirement", "REQ_MATH_001");
  RecordProperty("test_spec", "TS_FACTORIAL");
  
  // This test is named "Negative", and belongs to the "FactorialTest"
  // test case.
  EXPECT_EQ(1, Factorial(-5));
  EXPECT_EQ(1, Factorial(-1));
  EXPECT_GT(Factorial(-10), 0);

The annotation @Test negative factorial values, T_FACT_001, test creates a traceable link between the test code and the documentation.

GTest Properties (RecordProperty):

  • need_id: Links the test execution result to the test case need (T_FACT_001)

  • requirement: Links to the requirement being tested (REQ_MATH_001)

  • test_spec: Links to the test specification (TS_FACTORIAL)

These properties are included in the XML test report and enable automatic linking between test results and documentation needs.

Step 4: Show Code Traceability

Use sphinx-codelinks to display where test cases are implemented:

.. src-trace:: Test Case Implementation
    :project: eac-cpp
    :directory: src

This shows all annotated code locations, creating bidirectional links between documentation and source code.

Step 5: Integrate Test Reports

After running tests, integrate the test results using sphinx-test-reports:

# Build and run tests
cd src
cmake -S . -B build
cmake --build build
cd build
./eac_test --gtest_output=xml:test-results.xml

The test results XML file contains properties that link back to the test case needs, enabling complete traceability.

Traceability Matrix

Test Specifications to Test Cases

View which test cases implement each test specification:

ID

Title

Type

Status

Spec

Implements

DEBT_001

Technical Debt: Legacy API Endpoints

spec

open

T_FACT_001

Test negative factorial values

test

T_FACT_002

Test factorial of zero

test

T_FACT_003

Test factorial of positive numbers

test

T_PRIME_001

Test prime check for negative values

test

T_PRIME_002

Test prime check for trivial cases

test

T_PRIME_003

Test prime check for positive values

test

TS_FACTORIAL

Factorial Function Test Specification

test-spec

open

TS_PRIME

Prime Number Check Test Specification

test-spec

open

Test Cases to Implementation

View the connection between test cases and the code they test:

@startuml

' Nodes definition 

node "<size:12>Specification</size>\n**Technical Debt:**\n**Legacy API**\n**Endpoints**\n<size:10>DEBT_001</size>" as DEBT_001 [[../how-to-guides/arc42/index.html#DEBT_001]] #ec6dd7
node "<size:12>Test Case</size>\n**Test negative**\n**factorial**\n**values**\n<size:10>T_FACT_001</size>" as T_FACT_001 [[../how-to-guides/testing/index.html#T_FACT_001]] #A6BDD7
node "<size:12>Test Case</size>\n**Test factorial**\n**of zero**\n<size:10>T_FACT_002</size>" as T_FACT_002 [[../how-to-guides/testing/index.html#T_FACT_002]] #A6BDD7
node "<size:12>Test Case</size>\n**Test factorial**\n**of positive**\n**numbers**\n<size:10>T_FACT_003</size>" as T_FACT_003 [[../how-to-guides/testing/index.html#T_FACT_003]] #A6BDD7
node "<size:12>Test Case</size>\n**Test prime**\n**check for**\n**negative values**\n<size:10>T_PRIME_001</size>" as T_PRIME_001 [[../how-to-guides/testing/index.html#T_PRIME_001]] #A6BDD7
node "<size:12>Test Case</size>\n**Test prime**\n**check for**\n**trivial cases**\n<size:10>T_PRIME_002</size>" as T_PRIME_002 [[../how-to-guides/testing/index.html#T_PRIME_002]] #A6BDD7
node "<size:12>Test Case</size>\n**Test prime**\n**check for**\n**positive values**\n<size:10>T_PRIME_003</size>" as T_PRIME_003 [[../how-to-guides/testing/index.html#T_PRIME_003]] #A6BDD7
node "<size:12>Implementation</size>\n**Adding two**\n**integers**\n<size:10>IMPL_1</size>" as IMPL_1 [[../how-to-guides/trace-code/index.html#IMPL_1]] #fa8638
node "<size:12>Implementation</size>\n**Calculate**\n**Factorial**\n<size:10>IMPL_2</size>" as IMPL_2 [[../how-to-guides/trace-code/index.html#IMPL_2]] #fa8638
node "<size:12>Implementation</size>\n**Check if prime**\n<size:10>IMPL_3</size>" as IMPL_3 [[../how-to-guides/trace-code/index.html#IMPL_3]] #fa8638
node "<size:12>Implementation</size>\n**Clone C string**\n<size:10>IMPL_4</size>" as IMPL_4 [[../how-to-guides/trace-code/index.html#IMPL_4]] #fa8638
node "<size:12>Implementation</size>\n**Set C string**\n<size:10>IMPL_5</size>" as IMPL_5 [[../how-to-guides/trace-code/index.html#IMPL_5]] #fa8638

' Connection definition 

T_FACT_001 --> IMPL_2: links outgoing\n
T_FACT_002 --> IMPL_2: links outgoing\n
T_FACT_003 --> IMPL_2: links outgoing\n
T_PRIME_001 --> IMPL_3: links outgoing\n
T_PRIME_002 --> IMPL_3: links outgoing\n
T_PRIME_003 --> IMPL_3: links outgoing\n

@enduml

Coverage Analysis

To generate code coverage reports with line-level detail:

# Run tests with coverage
./scripts/test_with_coverage.sh

# View coverage report
open src/build/coverage_html/index.html

The coverage report shows:

  • Line Coverage: Which lines of code were executed during tests

  • Function Coverage: Which functions were called

  • Branch Coverage: Which conditional branches were taken

CI/CD Integration

Integrate testing into your CI/CD pipeline:

# .github/workflows/test.yml
- name: Build C++ Project
  run: |
    cd src
    cmake -S . -B build
    cmake --build build

- name: Run Tests
  run: |
    cd src/build
    ./eac_test --gtest_output=xml:test-results.xml

- name: Upload Test Results
  uses: actions/upload-artifact@v4
  with:
    name: test-results
    path: src/build/test-results.xml

Complete Traceability Flow

The complete flow from requirements to test results:

@startuml

' Nodes definition 

node "<size:12>Requirement</size>\n**Requirement**\n**page 1 nr 1**\n<size:10>REQ_1_1_ext</size>" as REQ_1_1_ext [[https://example.com/REQ_1_1_ext]] #FFB300
node "<size:12>Requirement</size>\n**Requirement**\n**page 1 nr 2**\n<size:10>REQ_1_2_ext</size>" as REQ_1_2_ext [[https://example.com/REQ_1_2_ext]] #FFB300
node "<size:12>Specification</size>\n**Technical Debt:**\n**Legacy API**\n**Endpoints**\n<size:10>DEBT_001</size>" as DEBT_001 [[../how-to-guides/arc42/index.html#DEBT_001]] #ec6dd7
node "<size:12>Requirement</size>\n**Requirement 1.1**\n<size:10>REQ_1_1</size>" as REQ_1_1 [[../how-to-guides/external_needs/index.html#REQ_1_1]] #FFB300
node "<size:12>Requirement</size>\n**Lane Detection**\n**Algorithm**\n<size:10>REQ_001</size>" as REQ_001 [[../how-to-guides/jira/index.html#REQ_001]] #FFB300
node "<size:12>Test Specification</size>\n**Factorial**\n**Function Test**\n**Specification**\n<size:10>TS_FACTORIAL</size>" as TS_FACTORIAL [[../how-to-guides/testing/index.html#TS_FACTORIAL]] #A6BDD7
node "<size:12>Test Specification</size>\n**Prime Number**\n**Check Test**\n**Specification**\n<size:10>TS_PRIME</size>" as TS_PRIME [[../how-to-guides/testing/index.html#TS_PRIME]] #A6BDD7
node "<size:12>Test Case</size>\n**Test negative**\n**factorial**\n**values**\n<size:10>T_FACT_001</size>" as T_FACT_001 [[../how-to-guides/testing/index.html#T_FACT_001]] #A6BDD7
node "<size:12>Test Case</size>\n**Test factorial**\n**of zero**\n<size:10>T_FACT_002</size>" as T_FACT_002 [[../how-to-guides/testing/index.html#T_FACT_002]] #A6BDD7
node "<size:12>Test Case</size>\n**Test factorial**\n**of positive**\n**numbers**\n<size:10>T_FACT_003</size>" as T_FACT_003 [[../how-to-guides/testing/index.html#T_FACT_003]] #A6BDD7
node "<size:12>Test Case</size>\n**Test prime**\n**check for**\n**negative values**\n<size:10>T_PRIME_001</size>" as T_PRIME_001 [[../how-to-guides/testing/index.html#T_PRIME_001]] #A6BDD7
node "<size:12>Test Case</size>\n**Test prime**\n**check for**\n**trivial cases**\n<size:10>T_PRIME_002</size>" as T_PRIME_002 [[../how-to-guides/testing/index.html#T_PRIME_002]] #A6BDD7
node "<size:12>Test Case</size>\n**Test prime**\n**check for**\n**positive values**\n<size:10>T_PRIME_003</size>" as T_PRIME_003 [[../how-to-guides/testing/index.html#T_PRIME_003]] #A6BDD7
node "<size:12>Requirement</size>\n**Factorial**\n**Function**\n<size:10>REQ_MATH_001</size>" as REQ_MATH_001 [[../how-to-guides/testing/requirements.html#REQ_MATH_001]] #FFB300
node "<size:12>Requirement</size>\n**Prime Number**\n**Detection**\n<size:10>REQ_MATH_002</size>" as REQ_MATH_002 [[../how-to-guides/testing/requirements.html#REQ_MATH_002]] #FFB300
node "<size:12>Implementation</size>\n**Adding two**\n**integers**\n<size:10>IMPL_1</size>" as IMPL_1 [[../how-to-guides/trace-code/index.html#IMPL_1]] #fa8638
node "<size:12>Implementation</size>\n**Calculate**\n**Factorial**\n<size:10>IMPL_2</size>" as IMPL_2 [[../how-to-guides/trace-code/index.html#IMPL_2]] #fa8638
node "<size:12>Implementation</size>\n**Check if prime**\n<size:10>IMPL_3</size>" as IMPL_3 [[../how-to-guides/trace-code/index.html#IMPL_3]] #fa8638
node "<size:12>Implementation</size>\n**Clone C string**\n<size:10>IMPL_4</size>" as IMPL_4 [[../how-to-guides/trace-code/index.html#IMPL_4]] #fa8638
node "<size:12>Implementation</size>\n**Set C string**\n<size:10>IMPL_5</size>" as IMPL_5 [[../how-to-guides/trace-code/index.html#IMPL_5]] #fa8638
node "<size:12>Requirement</size>\n**Requirement**\n**linking to**\n**source code**\n<size:10>REQ_0815</size>" as REQ_0815 [[../how-to-guides/trace-code/index.html#REQ_0815]] #FFB300
node "<size:12>Requirement</size>\n**Basic need**\n**example**\n<size:10>REQ_42</size>" as REQ_42 [[../how-to-guides/writing-requirements/index.html#REQ_42]] #FFB300
node "<size:12>Requirement</size>\n**Requirement**\n**linking to**\n**another**\n**requirement**\n<size:10>REQ_43</size>" as REQ_43 [[../how-to-guides/writing-requirements/index.html#REQ_43]] #FFB300

' Connection definition 

REQ_1_1 --> REQ_1_1_ext: links outgoing\n
T_FACT_001 --> TS_FACTORIAL: links outgoing\n
T_FACT_001 --> IMPL_2: links outgoing\n
T_FACT_002 --> TS_FACTORIAL: links outgoing\n
T_FACT_002 --> IMPL_2: links outgoing\n
T_FACT_003 --> TS_FACTORIAL: links outgoing\n
T_FACT_003 --> IMPL_2: links outgoing\n
T_PRIME_001 --> TS_PRIME: links outgoing\n
T_PRIME_001 --> IMPL_3: links outgoing\n
T_PRIME_002 --> TS_PRIME: links outgoing\n
T_PRIME_002 --> IMPL_3: links outgoing\n
T_PRIME_003 --> TS_PRIME: links outgoing\n
T_PRIME_003 --> IMPL_3: links outgoing\n
IMPL_1 --> REQ_0815: links outgoing\n
REQ_43 --> REQ_42: links outgoing\n

' Legend definition
legend
|= Color |= Type |
|<back:#FFB300> #FFB300 </back>| Requirement |
|<back:#ec6dd7> #ec6dd7 </back>| Specification |
|<back:#fa8638> #fa8638 </back>| Implementation |
|<back:#A6BDD7> #A6BDD7 </back>| Test Case |
|<back:#A6BDD7> #A6BDD7 </back>| Test Specification |
|<back:#C10020> #C10020 </back>| Person |
|<back:#CEA262> #CEA262 </back>| Team |
|<back:#817066> #817066 </back>| Release |
|<back:#4aac73> #4aac73 </back>| Architecture |
|<back:#F6768E> #F6768E </back>| Need |
|<back:#2d86c1> #2d86c1 </back>| SW_Architecture |
|<back:#a8f578> #a8f578 </back>| SW_Requirement |
|<back:#BFD8D2> #BFD8D2 </back>| Architecture Goal |
|<back:#FEDCD2> #FEDCD2 </back>| Constraint |
|<back:#DF744A> #DF744A </back>| Building Block |
|<back:#DCB239> #DCB239 </back>| Architecture Decision |
|<back:#BFD8D2> #BFD8D2 </back>| Quality Goal |
|<back:#ffffff> #ffffff </back>| Src-Trace |
|<back:#ffffff> #ffffff </back>| Test-File |
|<back:#cccccc> #cccccc </back>| Test-Suite |
|<back:#999999> #999999 </back>| Test-Case |
|<back:#cccccc> #cccccc </back>| Issue |
|<back:#aaaaaa> #aaaaaa </back>| PullRequest |
|<back:#888888> #888888 </back>| Commit |
endlegend

@enduml

This visualization shows:

  • Requirements drive test specifications

  • Test specifications lead to test cases

  • Test cases verify implementations

  • Everything is traceable and documented

Summary

By combining:

  • Sphinx-Needs: For requirements and test specifications

  • Sphinx-Codelinks: For linking documentation to source code

  • Sphinx-Test-Reports: For integrating test execution results

You create a fully traceable testing ecosystem where:

✅ Every test links to its specification ✅ Every test links to the code it verifies ✅ Test results are automatically integrated ✅ Coverage is measured and reported ✅ Traceability is bidirectional and complete

Additional Resources