Integrating with PyTest¶
Compatible with SpiraTest, SpiraTeam, SpiraPlan
This section describes how to use Spira in conjunction with python's pytest unit testing framework. The Spira pytest plugin enables the automated sending of unit test results from pytest to Spira with a specified Test Case, and (optionally), a release and/or test set as well.
The plugin supports multiple ways to map tests to Spira test cases:
- Environment variables for secure credential management
- Pytest markers for flexible test organization
- Class-level mapping for test suites
- Function-level mapping for individual tests
Installing the pytest plugin¶
This section outlines how to install the Spira plugin for pytest. It assumes that you already have a working installation of Spira v6.0 or later. If you have an earlier version of Spira you will need to upgrade to at least v6.0 before trying to use this plugin. You will also need to have Python (with pip) and pytest version 3.0 or later.
To obtain the latest version of the Spira plugin, simply run the following command: pip install pytest-spiratest
This command will install the latest version of the plugin straight from the Python Package Index (PyPI). Once the Spira plugin is successfully installed, all you need to do is configure the extension, then you can begin testing!
Configuring the pytest plugin¶
This section outlines how to configure the Spira plugin for pytest. It assumes that you are familiar with pytest, and already have some working tests configured.
Configuration Options¶
The plugin supports two configuration methods:
- Environment variables (recommended for credentials) - using a
.env.spirafile - Configuration file - using a
spira.cfgfile
You can use both methods together. Environment variables take priority over configuration file settings.
Option 1: Using Environment Variables (Recommended)¶
Create a .env.spira file in your test root folder:
# Required settings
SPIRA_URL=http://localhost/Spira
SPIRA_USERNAME=fredbloggs
SPIRA_TOKEN={XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX}
SPIRA_PROJECT_ID=1
# Optional settings
SPIRA_RELEASE_ID=5
SPIRA_TEST_SET_ID=1
Security Best Practice
Add .env.spira to your .gitignore file to keep credentials out of version control. Share a .env.spira.example file (without real credentials) with your team instead.
Option 2: Using Configuration File¶
Create a spira.cfg file in your test root folder:
[credentials]
# Following are required
url = localhost/Spira
username = fredbloggs
token = {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX}
project_id = 1
# Following are optional:
release_id = 5
test_set_id = 1
[test_cases]
# Default test case ID for unmapped tests
default = 20
# Function-level mapping
test_add_2 = 22
# Class-level mapping (all tests in class use this ID)
testcalculator = 25
[markers]
# Map pytest markers to test cases
smoke = 30
regression = 31
integration = 32
[settings]
# Optional: Enable verbose logging
verbose = false
Option 3: Combining Both (Recommended)¶
For best security and flexibility, use .env.spira for credentials and spira.cfg for test mappings:
.env.spira (keep this out of version control):
# Credentials - DO NOT commit to git
SPIRA_URL=http://localhost/Spira
SPIRA_USERNAME=fredbloggs
SPIRA_TOKEN={XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX}
SPIRA_PROJECT_ID=1
SPIRA_RELEASE_ID=5
SPIRA_TEST_SET_ID=1
spira.cfg (safe to commit to version control):
# No [credentials] section needed when using .env.spira!
[test_cases]
# Default test case ID for unmapped tests
default = 20
# Function-level mapping
test_add_2 = 22
# Class-level mapping (all tests in class use this ID)
testcalculator = 25
[markers]
# Map pytest markers to test cases
smoke = 30
regression = 31
integration = 32
Best Practice
This approach keeps sensitive credentials out of version control while allowing test mappings to be shared with the team. You only need .env.spira for credentials - the [credentials] section in spira.cfg is optional when using environment variables.
Priority
When both files exist, environment variables from .env.spira take priority over settings in spira.cfg. This allows you to override configuration per environment (dev, staging, production) without changing the config file.
Example: Different Environments
You can have different .env.spira files for different environments while keeping the same spira.cfg:
.env.spira.dev:
SPIRA_URL=http://dev-spira.company.com/Spira
SPIRA_USERNAME=dev-user
SPIRA_TOKEN={DEV-TOKEN}
SPIRA_PROJECT_ID=1
.env.spira.prod:
SPIRA_URL=http://spira.company.com/Spira
SPIRA_USERNAME=prod-user
SPIRA_TOKEN={PROD-TOKEN}
SPIRA_PROJECT_ID=5
Then copy the appropriate file before running tests:
# For development
cp .env.spira.dev .env.spira
pytest
# For production
cp .env.spira.prod .env.spira
pytest
Configuration Parameters¶
Credentials Section:
- url: The base url to your Spira installation, without a '/' at the end.
- username: The username you use to sign into Spira.
- token: Your RSS Token. Found in your profile page as the "RSS Token" field, you must have RSS Feeds enabled for this to work.
- project_id: The ID of the project you would like the test runs to be sent to
- release_id: OPTIONAL -- Use if you would like to associate the test run with a release.
- test_set_id: OPTIONAL -- Use if you would like to associate the test run with a test set.
Test Cases Section:
- default: The default test case ID for functions without an assigned test case
- \
: Maps a specific test function to a test case ID (e.g., test_add_2 = 22) - \
: Maps all tests in a class to a test case ID (e.g., testcalculator = 25)
Markers Section (Optional):
- \
: Maps all tests with a specific pytest marker to a test case ID (e.g., smoke = 30)
Settings Section (Optional):
- verbose: Enable detailed logging showing test case mappings and API calls (values:
true,false,yes,no,1,0,on,off) - enabled: Enable or disable Spira integration (values:
true,false,yes,no,1,0,on,off). Can be overridden by--spira-enabledor--spira-disabledCLI flags - batch_mode: Enable batch mode to post all results at once for better performance (values:
true,false,yes,no,1,0,on,off). Can be overridden by--spira-batchor--spira-no-batchCLI flags
Case Insensitivity
Function names, class names, and marker names in the configuration file are case-insensitive.
Mapping Tests to Spira Test Cases¶
The plugin provides multiple ways to map your pytest tests to Spira test cases. The mapping priority (from highest to lowest) is:
- @pytest.mark.spira_id() marker - Explicit test case ID in code
- Other pytest markers - Existing markers mapped in configuration
- Function name - Specific function mapping in configuration
- Class name - All tests in a class mapped in configuration
- Default - Fallback test case ID
Method 1: Using @pytest.mark.spira_id() Marker¶
Apply the @pytest.mark.spira_id() marker directly to test functions or classes for explicit control:
import pytest
# Map a single test function
@pytest.mark.spira_id(100)
def test_login():
assert True
# Map all tests in a class
@pytest.mark.spira_id(200)
class TestAuthentication:
def test_login(self):
# Maps to test case 200
assert True
def test_logout(self):
# Maps to test case 200
assert True
# Override class-level mapping
@pytest.mark.spira_id(300)
def test_password_reset(self):
# Maps to test case 300
assert True
When to Use
Use this method when you want the mapping visible in your test code, or when different tests in the same class need different mappings.
Method 2: Using Existing Pytest Markers¶
Map your existing pytest markers (like @pytest.mark.smoke, @pytest.mark.regression) to Spira test cases:
Configuration (spira.cfg):
[markers]
smoke = 100
regression = 101
integration = 102
critical = 103
Test Code:
import pytest
@pytest.mark.smoke
def test_critical_login():
# Maps to test case 100
assert True
@pytest.mark.regression
class TestCheckout:
# All tests in class map to test case 101
def test_add_to_cart(self):
assert True
def test_payment(self):
assert True
When to Use
Use this method when you already organize tests with pytest markers. Works seamlessly with pytest -m filtering.
Method 3: Function-Level Mapping¶
Map specific test functions in the configuration file:
Configuration (spira.cfg):
[test_cases]
default = 20
test_add_2 = 22
test_login = 25
Test Code:
def test_add_1():
# Uses default: 20
assert True
def test_add_2():
# Maps to test case 22
assert True
def test_login():
# Maps to test case 25
assert True
Method 4: Class-Level Mapping¶
Map entire test classes to a single test case:
Configuration (spira.cfg):
[test_cases]
default = 20
testcalculator = 25
testauthentication = 30
Test Code:
class TestCalculator:
def test_add(self):
# Maps to test case 25
assert True
def test_subtract(self):
# Maps to test case 25
assert True
class TestAuthentication:
def test_login(self):
# Maps to test case 30
assert True
def test_logout(self):
# Maps to test case 30
assert True
When to Use
Use this method to reduce configuration verbosity when all tests in a class should map to the same test case.
Method 5: Default Mapping¶
Any test without a specific mapping uses the default test case ID:
[test_cases]
default = 20
Complete Example¶
Here is a complete example showing all mapping methods:
Configuration (.env.spira):
SPIRA_URL=http://localhost/Spira
SPIRA_USERNAME=fredbloggs
SPIRA_TOKEN={XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX}
SPIRA_PROJECT_ID=1
Configuration (spira.cfg):
[test_cases]
default = 20
test_add_2 = 22
testcalculator = 25
[markers]
smoke = 30
regression = 31
Test File:
import pytest
# Function we are testing
def add(num1, num2):
return num1 + num2
# Uses default mapping (20)
def test_add_1():
assert add(1, 1) == 2
# Uses function mapping (22)
def test_add_2():
assert add(2, 1) == 3
# Uses marker mapping (30)
@pytest.mark.smoke
def test_add_smoke():
assert add(3, 3) == 6
# Uses explicit spira_id marker (100)
@pytest.mark.spira_id(100)
def test_add_special():
assert add(4, 4) == 8
# All tests use class mapping (25)
class TestCalculator:
def test_add(self):
assert add(5, 5) == 10
def test_subtract(self):
assert 10 - 5 == 5
# Override with explicit marker (200)
@pytest.mark.spira_id(200)
def test_multiply(self):
assert 2 * 3 == 6
# All tests use regression marker (31)
@pytest.mark.regression
class TestRegression:
def test_feature_a(self):
assert True
def test_feature_b(self):
assert True
Running Tests¶
Running the pytest (or py.test) command will run your unit tests, send the data to Spira, and show the results to you. Here is an example of the test_add_3 function inside Spira:

Command-Line Options¶
The plugin provides several CLI options to control behavior:
# Disable Spira integration for this run
pytest --spira-disabled
# Enable Spira integration (overrides config)
pytest --spira-enabled
# Dry run - show what would be posted without posting
pytest --spira-dry-run
# Enable batch mode - post all results at once (faster for large suites)
pytest --spira-batch
# Disable batch mode - post results individually
pytest --spira-no-batch
Batch Mode for Performance¶
For large test suites (100+ tests), batch mode provides significant performance improvements by posting all results in a single API call:
Enable batch mode:
pytest --spira-batch
Or in configuration:
[settings]
batch_mode = true
Performance comparison: - Individual mode: 100 tests = 100 API calls (~10s overhead) - Batch mode: 100 tests = 1 API call (~1s overhead) - Result: 50-70% faster for large test suites
When to Use Batch Mode
- ✅ CI/CD pipelines for faster builds
- ✅ Large test suites (100+ tests)
- ✅ Network-constrained environments
- ❌ Real-time monitoring (results appear all at once)
- ❌ Debugging individual tests
Dry Run Mode¶
Preview what would be posted to Spira without actually posting:
pytest --spira-dry-run
Example output:
[pytest-spiratest INFO] [DRY RUN] Would post test result for: test_login
[pytest-spiratest INFO] Test case ID: 100
[pytest-spiratest INFO] Status: 2 (PASSED)
[pytest-spiratest INFO] Duration: 0.45s
Use dry run to: - Verify test case mappings - Debug configuration issues - Test new configurations safely
Running Specific Test Categories¶
When using marker mapping, you can run specific test categories and have results automatically sent to the correct Spira test case:
# Run only smoke tests - results go to smoke test case
pytest -m smoke
# Run only regression tests - results go to regression test case
pytest -m regression
# Run critical tests
pytest -m critical
Priority Resolution Examples¶
Understanding how the plugin resolves test case mappings:
Example 1: spira_id marker overrides everything
@pytest.mark.spira_id(999) # This wins
@pytest.mark.smoke # Ignored
def test_login():
# Maps to 999
assert True
Example 2: Marker beats function name
# Config has: test_login = 100, smoke = 200
@pytest.mark.smoke
def test_login():
# Maps to 200 (marker beats function)
assert True
Example 3: Function name beats class name
# Config has: test_login = 100, testauth = 200
class TestAuth:
def test_login(self):
# Maps to 100 (function beats class)
assert True
Example 4: Class name beats default
# Config has: testauth = 200, default = 20
class TestAuth:
def test_logout(self):
# Maps to 200 (class beats default)
assert True
Migration from Earlier Versions¶
If you're upgrading from an earlier version of the plugin, your existing configuration will continue to work without changes. The old format of mapping class methods (MyClass.myFunction = 22) is still supported.
To take advantage of new features:
- Move credentials to .env.spira for better security
- Use class-level mapping to simplify configuration (instead of mapping each method individually)
- Use marker mapping if you already organize tests with pytest markers
- Use @pytest.mark.spira_id() for explicit control when needed
Troubleshooting¶
Tests not appearing in Spira?
- Verify credentials in
.env.spiraorspira.cfg - Check that project_id exists in Spira
- Ensure test case IDs are valid
- Enable verbose mode to see detailed logging
Wrong test case mapping?
- Check the priority order: spira_id marker > other markers > function name > class name > default
- Verify spelling of function/class/marker names in configuration
- Remember that names are case-insensitive in the config file
- Enable verbose mode to see which test case ID is being used
Environment variables not working?
- Ensure
.env.spirais in the same directory where you run pytest - Check variable names:
SPIRA_URL,SPIRA_USERNAME,SPIRA_TOKEN, etc. - Environment variables override
spira.cfgsettings
Verbose Mode and Logging¶
The plugin provides detailed logging to help debug issues.
Enabling Verbose Mode¶
Option 1: Configuration file (spira.cfg)
[settings]
verbose = true
Option 2: Environment variable (.env.spira)
SPIRA_VERBOSE=true
What Verbose Mode Shows¶
When enabled, you'll see:
- Configuration summary at startup
- Test case ID mapping for each test
- API call details and responses
- Success/failure status for each post
Example output:
[pytest-spiratest INFO] Spira integration initialized
[pytest-spiratest DEBUG] URL: http://localhost/spira
[pytest-spiratest DEBUG] Project ID: 1
[pytest-spiratest DEBUG] Default test case: 22
[pytest-spiratest DEBUG] Test 'test_login' mapped to Spira test case ID: 100
[pytest-spiratest DEBUG] Posting test result to Spira: http://localhost/spira/...
[pytest-spiratest DEBUG] Successfully posted test result for: test_login
Error Messages¶
Errors are always shown, even without verbose mode:
[pytest-spiratest ERROR] HTTP error 401 posting test result to Spira for test: test_login
[pytest-spiratest ERROR] URL: http://localhost/spira/Services/v6_0/RestService.svc/projects/1/test-runs/record
[pytest-spiratest ERROR] Test case ID: 100
[pytest-spiratest ERROR] Response: {"Message":"Invalid credentials"}
This helps you quickly identify: - Connection issues - Authentication problems - Invalid test case IDs - API errors
Debugging Tip
Enable verbose mode when setting up the integration or troubleshooting issues. Disable it in CI/CD for cleaner logs.
Have Questions or Need Assistance?¶
If you are an Inflectra customer, please contact our customer support at:
- Email: support@inflectra.com
- Help Desk: https://www.inflectra.com/Support/
Otherwise, please feel free to post a question on our public forums: