Skip to content

Testing Guide

Testing standards and practices for sigc.

Test Types

Type Location Purpose
Unit src/ alongside code Test individual functions
Integration tests/ Test components together
End-to-end tests/e2e/ Test full workflows
Benchmark benches/ Performance testing

Running Tests

All Tests

Bash
cargo test --workspace

Specific Crate

Bash
cargo test -p sig_parser
cargo test -p sig_runtime

Single Test

Bash
cargo test test_name

With Output

Bash
cargo test -- --nocapture

Integration Tests

Bash
cargo test --test integration

Benchmarks

Bash
cargo bench

Writing Unit Tests

Basic Test

Rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_zscore() {
        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
        let result = zscore(&values);

        assert_eq!(result.len(), 5);
        assert!((result[2] - 0.0).abs() < 1e-10); // Middle should be ~0
    }
}

Test Error Cases

Rust
#[test]
fn test_division_by_zero() {
    let result = divide(10.0, 0.0);
    assert!(result.is_err());
    assert!(matches!(result, Err(Error::DivisionByZero)));
}

Test with Setup

Rust
fn setup_test_data() -> DataFrame {
    DataFrame::new(vec![
        ("date", vec!["2024-01-01", "2024-01-02"]),
        ("price", vec![100.0, 102.0]),
    ])
}

#[test]
fn test_with_data() {
    let data = setup_test_data();
    let result = compute(&data);
    assert_eq!(result, expected);
}

Writing Integration Tests

Create files in tests/:

Rust
// tests/backtest_integration.rs

use sigc::Strategy;

#[test]
fn test_momentum_backtest() {
    let strategy = Strategy::from_file("strategies/momentum.sig").unwrap();
    let results = strategy.run().unwrap();

    assert!(results.sharpe_ratio() > 0.0);
    assert!(results.total_return() > -1.0);
}

Test Fixtures

Sample Data

Create test data in tests/fixtures/:

Text Only
tests/fixtures/
├── prices_small.csv
├── prices_with_gaps.csv
└── fundamentals.csv

Load Fixtures

Rust
fn load_test_prices() -> DataFrame {
    DataFrame::from_csv("tests/fixtures/prices_small.csv").unwrap()
}

Property-Based Testing

Use proptest for property-based tests:

Rust
use proptest::prelude::*;

proptest! {
    #[test]
    fn test_zscore_mean_zero(values in prop::collection::vec(any::<f64>(), 10..100)) {
        let zscores = zscore(&values);
        let mean: f64 = zscores.iter().sum::<f64>() / zscores.len() as f64;
        prop_assert!((mean - 0.0).abs() < 1e-10);
    }
}

Mocking

For external dependencies:

Rust
#[cfg(test)]
mod tests {
    use mockall::predicate::*;
    use super::*;

    mock! {
        BrokerClient {
            fn submit_order(&self, order: Order) -> Result<()>;
        }
    }

    #[test]
    fn test_execution() {
        let mut mock_broker = MockBrokerClient::new();
        mock_broker
            .expect_submit_order()
            .times(1)
            .returning(|_| Ok(()));

        let executor = Executor::new(mock_broker);
        let result = executor.execute(order);
        assert!(result.is_ok());
    }
}

Python Tests

Setup

Bash
cd pysigc
pip install -e ".[dev]"
pytest

Writing Tests

Python
# tests/test_run.py
import pysigc

def test_basic_run():
    results = pysigc.run("tests/fixtures/momentum.sig")
    assert results.sharpe_ratio is not None

def test_with_params():
    results = pysigc.run(
        "tests/fixtures/momentum.sig",
        params={"lookback": 60}
    )
    assert results.total_return > -1.0

Test Coverage

Generate Coverage Report

Bash
cargo tarpaulin --out Html

Coverage Targets

Area Target
Parser 90%+
Runtime 85%+
CLI 70%+

CI/CD Tests

Tests run automatically on: - Pull requests - Main branch pushes

Required to pass: - All unit tests - All integration tests - Clippy (no warnings) - rustfmt check

Best Practices

1. Test One Thing

Rust
// Good: Focused test
#[test]
fn test_momentum_calculates_return() {
    // Only tests return calculation
}

// Avoid: Testing multiple things
#[test]
fn test_momentum_everything() {
    // Tests return, zscore, ranking, portfolio...
}

2. Descriptive Names

Rust
// Good
#[test]
fn test_zscore_returns_zero_for_mean_value() { }

// Avoid
#[test]
fn test_zscore() { }

3. Arrange-Act-Assert

Rust
#[test]
fn test_compute_signal() {
    // Arrange
    let data = setup_data();
    let params = SignalParams::default();

    // Act
    let result = compute_signal(&data, &params);

    // Assert
    assert_eq!(result.len(), expected_len);
}

4. Test Edge Cases

  • Empty input
  • Single element
  • Negative values
  • NaN/Inf values
  • Boundary values

See Also