# Vary Course

## Vary Testing

Seven runnable lessons covering `test`, `observe`, edge cases, error paths, output, and naming.

### First tests

#### The smallest test

Verify one fact about one function.

```vary
pure def is_even(n: Int) -> Bool {
    return n % 2 == 0
}

test "4 is even" {
    observe is_even(4) == True
}

```

Run: `vary test tests/even_test.vary`

Expected output:

```text
Results: 1 passed, 0 failed
```

#### Multiple observations per test

Group several related facts under one named behaviour.

```vary
pure def normalize_path(path: Str) -> Str {
    if path.startswith("/") {
        return path
    }
    return "/" + path
}

test "normalize_path enforces a single leading slash" {
    observe normalize_path("api") == "/api"
    observe normalize_path("/api") == "/api"
    observe normalize_path("") == "/"
}

```

Run: `vary test tests/path_test.vary`

Expected output:

```text
Results: 1 passed, 0 failed
```

#### Multiple tests per file

Cover distinct behaviours with separate named tests.

```vary
pure def class_of(status: Int) -> Str {
    if status >= 500 {
        return "5xx"
    }
    if status >= 400 {
        return "4xx"
    }
    if status >= 200 {
        return "2xx"
    }
    return "other"
}

test "2xx range" {
    observe class_of(200) == "2xx"
}

test "4xx range" {
    observe class_of(404) == "4xx"
}

test "5xx range" {
    observe class_of(503) == "5xx"
}

test "below 200 is other" {
    observe class_of(100) == "other"
}

```

Run: `vary test tests/status_test.vary`

Expected output:

```text
Results: 4 passed, 0 failed
```

### Edge cases and errors

#### Test the boundaries

Assert both sides of every edge where behaviour changes.

```vary
pure def is_2xx(status: Int) -> Bool {
    return status >= 200 and status < 300
}

test "200 is the lower edge" {
    observe is_2xx(200) == True
    observe is_2xx(199) == False
}

test "299 is the upper edge" {
    observe is_2xx(299) == True
    observe is_2xx(300) == False
}

```

Run: `vary test tests/range_test.vary`

Expected output:

```text
Results: 2 passed, 0 failed
```

#### Test error paths

Use `observe throws` to assert a block raises.

```vary
def parse_port(s: Str) -> Int {
    let n = int(s)
    if n < 1 or n > 65535 {
        raise "port out of range: " + s
    }
    return n
}

test "valid port parses" {
    observe parse_port("8080") == 8080
}

test "out of range raises" {
    observe throws { parse_port("99999") }
}

test "non-numeric raises" {
    observe throws { parse_port("nope") }
}

```

Run: `vary test tests/port_test.vary`

Expected output:

```text
Results: 3 passed, 0 failed
```

### Running and reading

#### Read the test output

Locate the pass/fail summary line and understand the exit code.

```vary
pure def retry_delay_ms(attempt: Int) -> Int {
    if attempt <= 0 {
        return 100
    }
    if attempt >= 5 {
        return 16000
    }
    mut delay: Int = 100
    mut i: Int = 0
    while i < attempt {
        delay = delay * 2
        i = i + 1
    }
    return delay
}

test "first attempt is 100ms" {
    observe retry_delay_ms(0) == 100
}

test "doubles each attempt" {
    observe retry_delay_ms(1) == 200
    observe retry_delay_ms(2) == 400
    observe retry_delay_ms(3) == 800
}

test "saturates at 16000ms" {
    observe retry_delay_ms(5) == 16000
    observe retry_delay_ms(10) == 16000
}

```

Run: `vary test tests/backoff_test.vary`

Expected output:

```text
Results: 3 passed, 0 failed
```

#### Names are documentation

Write test names that read like the spec.

```vary
pure def truncate(s: Str, limit: Int) -> Str {
    if len(s) <= limit {
        return s
    }
    return s[:limit - 1] + "…"
}

test "truncate returns the input when it fits inside the limit" {
    observe truncate("hello", 10) == "hello"
}

test "truncate replaces the last character with an ellipsis when too long" {
    observe truncate("hello world", 5) == "hell…"
}

test "truncate handles the exact-fit boundary without modification" {
    observe truncate("hello", 5) == "hello"
}

```

Run: `vary test tests/truncate_test.vary`

Expected output:

```text
Results: 3 passed, 0 failed
```

