Link sphinx-needs requirements (needs.json) to GoogleTest JUnit XML and emit an RTM report.
Project description
RTM – Requirements Traceability for Sphinx-Needs + GTest
RTM is a small, opinionated tool that links software requirements (from sphinx-needs) to unit tests (from GoogleTest JUnit XML), producing a clear, machine-readable requirements traceability matrix (RTM) and a human-readable summary.
It is designed to be:
- Simple – no Sphinx plugins, no test framework hooks
- Deterministic – all logic is explicit and inspectable
- Extensible – users control how requirements are extracted from tests
- CI-friendly – JSON output for downstream tooling
The CLI is the primary interface, but all components are importable as a Python module.
What this tool does
RTM answers questions like:
- Which requirements have tests?
- Which requirements are missing tests?
- Which tests claim to cover requirements that don’t exist?
- Which tests cover multiple requirements?
- Are all tests for a given requirement passing?
It does not:
- Enforce any particular testing style
- Modify your test framework
- Require Sphinx to know about tests
- Require tests to be modeled as Sphinx needs
Inputs
1. Requirements (needs.json)
Generated by sphinx-needs when you build your documentation.
Example location:
docs/_build/needs/needs.json
Only needs of type req are considered.
2. Test results (JUnit XML)
Generated by GoogleTest, for example:
--gtest_output=xml:build/test-results/junit.xml
RTM reads:
- suite name (
classname) - test name (
name) - status (
pass,fail,error,skip) - optional
<properties>blocks
Default behavior (very dumb, by design)
By default, RTM assumes:
The test name itself is the requirement ID.
Example:
TEST(MathUtilsTest, REQ_MATH_ADD_001)
If your test names include extra text:
REQ_MATH_ADD_001_AddPositiveNumbers
…the default extractor will not match this. This is intentional.
The philosophy is:
- Start with a trivial, predictable default
- Let users explicitly define smarter extraction logic
Custom requirement extraction (the key feature)
Instead of hard-coding regex rules, RTM lets you define how requirements are extracted from each test.
You provide a small Python function that receives:
(suite: str, name: str, properties: dict[str, list[str]])
…and returns:
list[str] # requirement IDs
RTM then:
- Validates extracted IDs against
needs.json - Links known IDs
- Reports unknown / missing / unmapped cases separately
Example: properties-first extractor (recommended)
If your JUnit XML includes properties like:
<property name="requirement" value="REQ_MATH_ADD_001"/>
Create a file extractor_props_first.py:
def extract_requirements(suite, name, properties):
# Prefer explicit metadata
vals = properties.get("requirement")
if vals:
return vals
# Fallback (optional)
return []
Run RTM with:
rtm-link \
--needs docs/_build/needs/needs.json \
--junit build/test-results/junit.xml \
--out rtm_report.json \
--extractor extractor_props_first.py \
--print
This is the cleanest long-term approach:
- No regex
- No naming conventions
- Stable under renames
CLI usage
rtm-link \
--needs PATH/TO/needs.json \
--junit PATH/TO/junit.xml \
--out PATH/TO/rtm_report.json \
[--extractor extractor.py] \
[--print]
Options:
--needs– sphinx-needsneeds.json--junit– JUnit XML from gtest--out– output JSON report--extractor– custom Python extractor module--print– print human-readable report to stdout
Output
Human-readable summary
Printed with --print, e.g.:
=== RTM Link Summary ===
Requirements: 3
With >=1 test: 3
Missing tests: 0
All tests passing: 3
Any test failing: 0
Tests: 3
Unmapped tests (no IDs extracted): 0
Multi-mapped tests: 0
Tests w/ unknown req IDs: 0
Tests w/ only unknown IDs: 0
Unknown req IDs (unique): 0
JSON report
Written to --out, suitable for CI, dashboards, or custom checks.
Includes:
- Summary counts
- Per-requirement test linkage
- Unmapped tests
- Multi-mapped tests
- Unknown requirement references
- Missing / failing requirements
Semantics (important)
RTM distinguishes these cases explicitly:
-
Unmapped test
Extractor returned no IDs at all. -
Unknown requirement reference
Extractor returned IDs that do not exist inneeds.json. -
Only unknown requirements
Extractor returned IDs, but none were valid.
These are intentionally separate failure modes.
Using RTM as a Python module
RTM is fully importable; the CLI is just a thin wrapper.
Example:
from pathlib import Path
from rtm_needs import NeedsJsonReader, JUnitReader, Linker
reqs = NeedsJsonReader().read(Path("docs/_build/needs/needs.json"))
tests = JUnitReader().read(Path("build/test-results/junit.xml"))
report = Linker().link(reqs=reqs, tests=tests)
print(report)
With a custom extractor:
def extract_requirements(suite, name, properties):
return properties.get("requirement", [])
report = Linker().link(reqs=reqs, tests=tests, extractor=extract_requirements)
What RTM is not (by design)
- Not a requirements management system
- Not a test framework
- Not a Sphinx extension
- Not a policy engine
RTM is a bridge between two existing artifacts:
- Requirements written for humans
- Tests written for machines
Roadmap (non-binding)
Possible future additions:
- Optional CI failure policies
- Built-in extractors (name prefix, delimiters, etc.)
- Coverage percentages / thresholds
- Alternate output formats (Markdown, CSV)
- Small test suite
All intentionally deferred until real usage demands them.
License
MIT (or your preferred OSS license).
Philosophy
Traceability should be explicit, inspectable, and boring.
If it’s surprising, it’s wrong.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rtm_needs-0.2.0.tar.gz.
File metadata
- Download URL: rtm_needs-0.2.0.tar.gz
- Upload date:
- Size: 13.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c146ee16a81c1be158a7747b476f0c413886e3a0046682c902de9de37883a07
|
|
| MD5 |
52e975fb942ff4c89ed3d7808b893afd
|
|
| BLAKE2b-256 |
0b4e9035202830bf93fadd6f23544a435127fb0577773617b11540d37d4ce0d8
|
File details
Details for the file rtm_needs-0.2.0-py3-none-any.whl.
File metadata
- Download URL: rtm_needs-0.2.0-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1de280f8194d5c142e164dd01582e4caeecbd89d9e420822eeddb7d5ceec4f8d
|
|
| MD5 |
fecb3bd7d8795d2d626ce6e15a230def
|
|
| BLAKE2b-256 |
ba72ea0257c92b12cfe7cff26882d1572654b0d9b3be66d7f3360132282a4545
|