Skip to main content

Testing Guide

CSV to QLab uses pytest for testing with comprehensive coverage of the configuration-driven architecture.

Test Suite Overview

Total Coverage: 86%

The test suite includes:

  • 50 total tests
  • 28 OSC configuration tests
  • 17 CSV parsing tests
  • 5 integration tests

Running Tests

Prerequisites

# Activate virtual environment
source env/bin/activate

# Install test dependencies
pip install -r requirements.txt

Run All Tests

# Run all tests
python -m pytest

# Run with verbose output
python -m pytest -v

# Run with coverage report
python -m pytest --cov=app --cov-report=term-missing

Run Specific Test Files

# OSC configuration tests only
python -m pytest app/tests/test_osc_config.py -v

# CSV parser tests only
python -m pytest app/tests/test_csv_parser.py -v

# Integration tests only
python -m pytest app/tests/test_app.py -v

Run Specific Test Classes or Functions

# Run specific test class
python -m pytest app/tests/test_osc_config.py::TestDuplicatePropertyNames -v

# Run specific test function
python -m pytest app/tests/test_osc_config.py::TestDuplicatePropertyNames::test_no_duplicate_property_names_global_vs_cue_specific -v

Test Files

test_osc_config.py

Tests for OSC configuration loading and validation.

Critical Tests:

  • Duplicate property name detection - Prevents silent failures
  • ✅ Property validation (ranges, enums, types)
  • ✅ Version-specific properties (QLab 4 vs 5)
  • ✅ Auto-properties
  • ✅ Conditional properties

Example:

def test_no_duplicate_property_names_global_vs_cue_specific():
"""Verify no property names overlap between global and cue-specific"""
# This test prevents configuration errors where the same property
# name exists in both global_properties and cue_type_properties,
# which would cause the global property to always win in lookups

test_csv_parser.py

Tests for CSV parsing and OSC message generation.

Covered Areas:

  • CSV parsing and header normalization
  • Property processing
  • Passcode handling
  • Cue type validation
  • Edge cases (unicode, special characters)

test_app.py

Integration tests for the Flask application.

Covered Areas:

  • HTTP endpoints
  • File upload handling
  • QLab version handling
  • Error responses

Critical Tests Explained

1. Duplicate Property Detection

Why it's critical: If a property name exists in both global_properties and a cue type's properties, the global property always wins. This causes silent failures where cue-specific properties are never used.

Test Location: test_osc_config.py::TestDuplicatePropertyNames

What it checks:

# Fails if any property name appears in both:
global_props = {"level": {...}}
cue_type_props = {"audio": {"level": {...}}} # CONFLICT!

2. Property Validation

Why it's critical: Invalid values sent to QLab can cause cues to be created with wrong settings.

Test Location: test_osc_config.py::TestPropertyValidation

What it checks:

  • Numeric ranges (e.g., continueMode must be 0-2)
  • Valid values lists (e.g., color must be from approved list)
  • Type conversion (int, float, bool, string)

3. Version Isolation

Why it's critical: QLab 4 and 5 have different OSC properties, especially for network cues.

Test Location: test_osc_config.py::TestVersionSpecificProperties

What it checks:

  • QLab 5 customstring not available in QLab 4
  • QLab 4 messagetype not available in QLab 5
  • Version-specific properties don't leak across versions

Writing New Tests

Test Structure

import pytest
from osc_config import OSCConfig

class TestNewFeature:
"""Test description"""

def test_feature_works(self):
"""Specific test case"""
config = OSCConfig()
result = config.some_method()
assert result is not None

Using Fixtures

@pytest.fixture
def config():
"""Reusable OSC config"""
return OSCConfig()

def test_with_fixture(config):
assert config is not None

Mocking External Dependencies

from unittest.mock import Mock, patch

def test_with_mock():
with patch('csv_parser.udp_client.UDPClient') as mock_client:
mock_client.return_value = Mock()
# Test code that uses UDP client

Coverage Goals

ModuleTargetCurrent
osc_config.py95%+94% ✅
csv_parser.py90%+100% ✅
application.py80%+82% ✅
Overall85%+86% ✅

Adding Tests for New Properties

When adding a new property to qlab_osc_config.json:

1. Add Validation Test

def test_new_property_validation(config):
"""Test the new property validates correctly"""
msg = config.build_osc_message('newproperty', 'valid_value')
assert msg is not None

msg = config.build_osc_message('newproperty', 'invalid_value')
assert msg is None

2. Update Duplicate Detection Test

The duplicate detection test automatically checks all properties, so no changes needed if following naming conventions.

3. Add Type Conversion Test (if applicable)

def test_new_property_type_conversion(config):
"""Test type conversion for new property"""
# For int properties
msg = config.build_osc_message('newproperty', '5')
assert msg is not None

Continuous Integration

Tests run automatically on GitHub Actions for:

  • Pull requests
  • Pushes to main branch
  • Release workflows

CI Configuration

See .github/workflows/pytest.yml for CI setup.

Test Dependencies

From requirements.txt:

  • pytest>=8.0.0 - Testing framework
  • pytest-cov>=4.1.0 - Coverage reporting

Common Test Patterns

Testing Configuration Loading

def test_config_loads():
config = OSCConfig()
assert config.global_properties is not None
assert config.cue_type_properties is not None

Testing OSC Message Building

def test_build_message():
config = OSCConfig()
msg = config.build_osc_message('name', 'Test Cue')
assert msg is not None
built = msg.build()
assert built is not None

Testing CSV Parsing

from unittest.mock import Mock, patch

def test_csv_parsing():
with patch('csv_parser.udp_client.UDPClient') as mock_client:
csv_content = MockFileStorage("Number,Type,Name\n1,audio,Test")
send_csv('127.0.0.1', csv_content, 5, '')
assert mock_client.return_value.send.called

Debugging Failed Tests

Verbose Output

python -m pytest -vv  # Extra verbose
python -m pytest -s # Show print statements

Run Single Test

python -m pytest app/tests/test_osc_config.py::test_name -vv

Use pytest.set_trace() for Debugging

def test_something():
import pytest
pytest.set_trace() # Drops into debugger
# Test code

Test Coverage Reports

Terminal Report

python -m pytest --cov=app --cov-report=term-missing

HTML Report

python -m pytest --cov=app --cov-report=html
open htmlcov/index.html

Coverage by Module

python -m pytest --cov=app --cov-report=term-missing | grep app/

Best Practices

  1. Test behavior, not implementation - Test what the code does, not how it does it
  2. Use descriptive test names - test_no_duplicate_property_names_global_vs_cue_specific is better than test_duplicates
  3. One assertion per test (when possible) - Makes failures easier to debug
  4. Mock external dependencies - Don't make real network calls or file I/O in unit tests
  5. Test edge cases - Empty strings, unicode, very long values, etc.
  6. Keep tests fast - Mock slow operations, avoid sleep()

Troubleshooting

Import Errors

# Make sure you're in the project root
cd /path/to/csv_to_qlab

# Make sure virtual env is activated
source env/bin/activate

# Reinstall dependencies
pip install -r requirements.txt

Coverage Not Working

# Install coverage separately
pip install pytest-cov

# Check pytest plugins
python -m pytest --version

Mocking Errors

# Use correct import path
# ❌ Wrong: with patch('osc_config') as mock:
# ✅ Right: with patch('csv_parser.get_osc_config') as mock:

See Also