# Testing Strategy

Every public function must have comprehensive test coverage.

## 10.1 Unit Tests (inline per module)

Every public function has at least:
- A happy-path test
- An edge case (empty string, zero, negative numbers, Unicode input)
- A `#[should_panic]` or `Err` assertion where appropriate

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

    #[test]
    fn test_to_snake_case_basic() {
        assert_eq!(to_snake_case("HelloWorld"), "hello_world");
        assert_eq!(to_snake_case("XMLParser"),  "xml_parser");
        assert_eq!(to_snake_case("TestV2"),     "test_v2");
        assert_eq!(to_snake_case(""),           "");
    }

    #[test]
    fn test_truncate_complete_words() {
        let s = "This is a very long title";
        assert_eq!(truncate(s, 10, true, "..."), "This is a...");
        assert_eq!(truncate(s, 10, false, "..."), "This is a ...");
    }

    #[test]
    fn test_slug_unicode() {
        assert_eq!(slug("hello ♥ world", '-'), "hello-love-world");
    }
}
```

## 10.2 Property-Based Tests (`proptest`)

```rust
use proptest::prelude::*;

proptest! {
    /// round-trip: snake → camel → snake should be stable
    #[test]
    fn prop_snake_camel_roundtrip(s in "[a-z][a-z0-9]{0,20}(_[a-z][a-z0-9]{0,10})*") {
        let result = to_snake_case(&to_camel_case(&s));
        prop_assert_eq!(result, s);
    }

    /// truncate never returns more chars than the limit
    #[test]
    fn prop_truncate_length(s in ".*", limit in 0usize..200) {
        let result = truncate(&s, limit, false, "");
        prop_assert!(result.chars().count() <= limit + 3);  // +3 for suffix
    }

    /// unique preserves all elements that appeared at least once
    #[test]
    fn prop_unique_subset(arr in prop::collection::vec(0i32..100, 0..50)) {
        let result = unique(&arr);
        for x in &result {
            prop_assert!(arr.contains(x));
        }
    }
}
```

## 10.3 Benchmarks (Criterion)

```rust
// benches/string_bench.rs
use criterion::{criterion_group, criterion_main, Criterion};
use rok_utils::str::*;

fn bench_slug(c: &mut Criterion) {
    let input = "The Quick Brown Fox Jumps Over The Lazy Dog";
    c.bench_function("slug", |b| b.iter(|| slug(input, '-')));
}

fn bench_fluent_chain(c: &mut Criterion) {
    c.bench_function("fluent_chain", |b| {
        b.iter(|| {
            Str::of("  Hello World  ")
                .trim()
                .slug()
                .truncate(30)
                .value()
        })
    });
}

criterion_group!(benches, bench_slug, bench_fluent_chain);
criterion_main!(benches);
```

## 10.4 Doctests as Living Docs

Every public function has a `/// # Examples` block that doubles as a doctest:

```rust
/// Convert a string to snake_case.
///
/// # Examples
///
/// ```rust
/// use rok_utils::str::to_snake_case;
///
/// assert_eq!(to_snake_case("HelloWorld"), "hello_world");
/// assert_eq!(to_snake_case("XMLParser"),  "xml_parser");
/// assert_eq!(to_snake_case(""),           "");
/// ```
pub fn to_snake_case(s: &str) -> String { ... }
```