Testing
Testing guidelines for Trial Submission Studio contributions.
Test Types
Unit Tests
Test individual functions and methods:
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_column_name_removes_spaces() {
let result = normalize_column_name("Patient Age");
assert_eq!(result, "PATIENT_AGE");
}
}
}
Integration Tests
Test interactions between modules:
#![allow(unused)]
fn main() {
// tests/integration_test.rs
use tss_ingest::CsvReader;
use tss_validate::Validator;
#[test]
fn validate_imported_data() {
let data = CsvReader::read("tests/data/sample.csv").unwrap();
let results = Validator::validate(&data, "DM").unwrap();
assert!(results.errors().is_empty());
}
}
Snapshot Tests
Use insta for output stability:
#![allow(unused)]
fn main() {
use insta::assert_snapshot;
#[test]
fn export_produces_expected_output() {
let output = export_to_string(&data);
assert_snapshot!(output);
}
}
Property Tests
Use proptest for edge cases:
#![allow(unused)]
fn main() {
use proptest::prelude::*;
proptest! {
#[test]
fn similarity_is_symmetric(a in ".*", b in ".*") {
let ab = calculate_similarity(&a, &b);
let ba = calculate_similarity(&b, &a);
assert!((ab - ba).abs() < 0.001);
}
}
}
Running Tests
All Tests
cargo test
Specific Crate
cargo test --package xport
Specific Test
cargo test test_name
With Output
cargo test -- --nocapture
Release Mode
cargo test --release
Test Organization
File Structure
crates/tss-validate/
├── src/
│ ├── lib.rs
│ └── rules/
│ └── structural.rs
└── tests/
├── structural_rules_test.rs
└── data/
└── sample_dm.csv
Inline Tests
For simple unit tests:
#![allow(unused)]
fn main() {
// src/normalize.rs
pub fn normalize(s: &str) -> String {
s.trim().to_uppercase()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize() {
assert_eq!(normalize(" hello "), "HELLO");
}
}
}
External Tests
For integration tests:
#![allow(unused)]
fn main() {
// tests/validation_integration.rs
use tss_validate::*;
#[test]
fn full_validation_workflow() {
// Integration test code
}
}
Test Data
Location
Test data files are in:
mockdata/- Shared test datasetscrates/*/tests/data/- Crate-specific test data
Sample Data
STUDYID,DOMAIN,USUBJID,SUBJID,AGE,SEX
ABC123,DM,ABC123-001,001,45,M
ABC123,DM,ABC123-002,002,38,F
Sensitive Data
Never commit real clinical trial data. Use:
- Synthetic/mock data only
- Anonymized examples
- Generated test cases
Writing Good Tests
Structure (AAA Pattern)
#![allow(unused)]
fn main() {
#[test]
fn test_validation_rule() {
// Arrange - set up test data
let data = create_test_dataframe();
let validator = Validator::new();
// Act - perform the operation
let results = validator.validate(&data);
// Assert - verify results
assert_eq!(results.len(), 1);
assert_eq!(results[0].severity, Severity::Error);
}
}
Descriptive Names
#![allow(unused)]
fn main() {
// Good
#[test]
fn returns_error_when_usubjid_is_missing() { ... }
#[test]
fn accepts_valid_iso8601_date_format() { ... }
// Avoid
#[test]
fn test1() { ... }
#[test]
fn it_works() { ... }
}
Test Edge Cases
#![allow(unused)]
fn main() {
#[test]
fn handles_empty_dataframe() { ... }
#[test]
fn handles_null_values() { ... }
#[test]
fn handles_unicode_characters() { ... }
#[test]
fn handles_maximum_length_values() { ... }
}
Test Error Conditions
#![allow(unused)]
fn main() {
#[test]
fn returns_error_for_invalid_input() {
let result = process_file("nonexistent.csv");
assert!(result.is_err());
}
#[test]
fn error_contains_helpful_message() {
let err = process_file("bad.csv").unwrap_err();
assert!(err.to_string().contains("parse error"));
}
}
Snapshot Testing
With Insta
#![allow(unused)]
fn main() {
use insta::assert_snapshot;
#[test]
fn xpt_header_format() {
let header = generate_header(&metadata);
assert_snapshot!(header);
}
}
Updating Snapshots
# Review and update snapshots
cargo insta review
CI Testing
Automated Checks
Every PR runs:
cargo test- All testscargo clippy- Lintingcargo fmt --check- Formatting
Test Matrix
Tests run on:
- Ubuntu (primary)
- macOS (future)
- Windows (future)
Test Coverage
Goal
Aim for high coverage on critical paths:
- Validation rules
- Data transformations
- File I/O
Not Required
100% coverage isn’t required. Focus on:
- Business logic
- Error handling
- Edge cases
Next Steps
- Pull Requests - Submit your changes
- Coding Standards - Code style