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.

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:

Results: 1 passed, 0 failed

Multiple observations per test

Group several related facts under one named behaviour.

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:

Results: 1 passed, 0 failed

Multiple tests per file

Cover distinct behaviours with separate named tests.

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:

Results: 4 passed, 0 failed

Edge cases and errors

Test the boundaries

Assert both sides of every edge where behaviour changes.

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:

Results: 2 passed, 0 failed

Test error paths

Use observe throws to assert a block raises.

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:

Results: 3 passed, 0 failed

Running and reading

Read the test output

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

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:

Results: 3 passed, 0 failed

Names are documentation

Write test names that read like the spec.

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:

Results: 3 passed, 0 failed