minio-rs/docs/TESTING_STRATEGY.md
Henk-Jan Lebbink ec0e79214f
refactor: consolidate codebase structure and add testing infrastructure (#194)
Reorganize project structure to improve maintainability and add comprehensive
  testing documentation and tooling support.
2025-11-19 11:09:22 -08:00

301 lines
7.1 KiB
Markdown

# MinIO Rust SDK Testing Strategy
## Overview
The MinIO Rust SDK uses a comprehensive testing approach combining unit tests, property-based tests, and integration tests to ensure reliability and correctness.
## Test Categories
### 1. Unit Tests (Primary Focus)
**Location:** `src/madmin/types/*.rs`, inline `#[cfg(test)]` modules
**Purpose:** Test individual components in isolation
- Type serialization/deserialization
- Builder pattern correctness
- Response parsing
- Validation logic
**Coverage Goal:** >90% for library code
**Example:**
```rust
#[test]
fn test_batch_job_type_serialization() {
let job_type = BatchJobType::Replicate;
let json = serde_json::to_string(&job_type).unwrap();
assert_eq!(json, "\"replicate\"");
}
```
### 2. Error Path Tests
**Location:** `src/madmin/types/error_tests.rs`
**Purpose:** Verify error handling and edge cases
- Invalid JSON deserialization
- Missing required fields
- Type mismatches
- Boundary conditions
- Unicode and special characters
- Malformed data
**Coverage Goal:** All error paths in critical code
**Example:**
```rust
#[test]
fn test_invalid_json_batch_job_type() {
let invalid_json = "\"invalid_type\"";
let result: Result<BatchJobType, _> = serde_json::from_str(invalid_json);
assert!(result.is_err(), "Should fail on invalid batch job type");
}
```
### 3. Property-Based Tests
**Location:** `src/madmin/builders/property_tests.rs`
**Tool:** `quickcheck` crate
**Purpose:** Test properties that should hold for arbitrary inputs
- Builder idempotence
- Validation consistency
- No panics on valid inputs
- Encoding/decoding round-trips
**Coverage Goal:** Key invariants and properties
**Example:**
```rust
quickcheck! {
fn prop_bucket_name_no_panic(name: String) -> TestResult {
if name.is_empty() {
return TestResult::discard();
}
let _result = validate_bucket_name(&name);
TestResult::passed()
}
}
```
### 4. Integration Tests
**Location:** `tests/` directory
**Purpose:** Test end-to-end workflows with live MinIO server
- Client initialization
- Request execution
- Response handling
- Multi-step operations
**Coverage Goal:** Critical user workflows
**Note:** Integration tests are **NOT** counted in unit test coverage metrics as they require external infrastructure.
**Example:**
```rust
#[tokio::test]
#[ignore] // Run only when MinIO server is available
async fn test_list_buckets() {
let client = create_test_client();
let buckets = client.list_buckets().send().await.unwrap();
assert!(buckets.buckets.len() >= 0);
}
```
## What NOT to Test
### 1. Client Execution Methods
- Methods in `src/madmin/client/` that call `.send()`
- These require live server and belong in integration tests
- Focus unit tests on request building, not execution
### 2. Trivial Code
- Simple getter/setter methods
- Derived trait implementations (Debug, Clone, etc.)
- Pass-through wrapper functions
### 3. External Dependencies
- `reqwest` HTTP client behavior
- `serde_json` serialization correctness
- `tokio` runtime functionality
## Test Organization
### File Structure
```
src/
├── madmin/
│ ├── types/
│ │ ├── user.rs # Type definitions + inline tests
│ │ ├── batch.rs # Type definitions + inline tests
│ │ └── error_tests.rs # Centralized error path tests
│ ├── builders/
│ │ ├── user_management/ # Builder implementations
│ │ └── property_tests.rs # Property-based tests
│ └── client/ # NO unit tests (integration only)
tests/
└── integration_tests.rs # End-to-end tests (ignored by default)
```
### Test Naming Conventions
**Unit Tests:**
- `test_<functionality>_<scenario>`
- Example: `test_user_serialization_with_utf8`
**Error Tests:**
- `test_<error_condition>`
- Example: `test_invalid_json_batch_job_type`
**Property Tests:**
- `prop_<property_being_tested>`
- Example: `prop_builder_idempotent`
## Running Tests
### All Tests
```bash
cargo test
```
### Unit Tests Only (Fast)
```bash
cargo test --lib
```
### Specific Test Module
```bash
cargo test --lib types::error_tests
```
### Property-Based Tests
```bash
cargo test --lib property_tests
```
### Integration Tests (Requires MinIO Server)
```bash
cargo test --test integration_tests -- --ignored
```
### Coverage Report
```bash
cargo llvm-cov --lib --tests --html --output-dir target/coverage
```
## Coverage Goals
### Overall Target: 85%+
**By Module:**
- `src/madmin/types/`: 95%+ (high value, easy to test)
- `src/madmin/builders/`: 90%+ (core functionality)
- `src/madmin/response/`: 90%+ (parsing critical)
- `src/madmin/client/`: 20%+ (mostly integration tests)
- `src/s3/`: 85%+ (established S3 client)
### Acceptable Gaps
- Client method bodies (integration test coverage)
- Error display formatting
- Debug implementations
- Example code in doc comments
## Adding New Tests
### For New Type Definitions
1. Add inline serialization test
2. Add to error_tests.rs for edge cases
3. Consider property test if validation exists
### For New Builders
1. Test required parameter validation
2. Test optional parameter combinations
3. Add property test for invariants
4. Verify request URL/headers/body
### For New Response Types
1. Test successful parsing with sample JSON
2. Test error cases (missing fields, wrong types)
3. Test optional field handling
## Continuous Integration
### Pre-Commit Checklist
```bash
cargo fmt --all --check
cargo clippy -- -D warnings
cargo test --lib
```
### CI Pipeline
```yaml
- Run: cargo test --lib --all-features
- Coverage: cargo llvm-cov --lib --tests
- Minimum: 85% coverage required
```
## Best Practices
### DO:
- ✅ Test error paths explicitly
- ✅ Use property tests for validation logic
- ✅ Test edge cases (empty, null, oversized)
- ✅ Keep tests focused and independent
- ✅ Use descriptive test names
### DON'T:
- ❌ Test external library behavior
- ❌ Require live server for unit tests
- ❌ Test implementation details
- ❌ Write flaky tests with timeouts
- ❌ Duplicate coverage across test types
## Debugging Test Failures
### View Detailed Output
```bash
cargo test --lib -- --nocapture test_name
```
### Run Single Test
```bash
cargo test --lib test_name -- --exact
```
### Debug Coverage Gaps
```bash
cargo llvm-cov --lib --tests --html
# Open target/coverage/index.html
```
## Maintenance
### Regular Tasks
- Review coverage reports monthly
- Update tests when APIs change
- Remove obsolete tests
- Refactor duplicated test code
### When Coverage Drops
1. Identify uncovered code with llvm-cov HTML report
2. Assess if coverage gap is acceptable (client methods, trivial code)
3. Add targeted tests for critical uncovered paths
4. Document intentional coverage exclusions
## Resources
- [Rust Book - Testing](https://doc.rust-lang.org/book/ch11-00-testing.html)
- [quickcheck Documentation](https://docs.rs/quickcheck/)
- [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov)
## Questions?
For testing strategy questions, see:
- [CONTRIBUTING.md](CONTRIBUTING.md) - General contribution guidelines
- [CLAUDE.md](CLAUDE.md) - Code quality standards