Step 10. Tests
Why#
If tests are a separate framework with its own conventions, they get written less often. In OxL test is a language declaration: the compiler
knows about tests, leaves them out of the ordinary build and checks them together with the rest of the code. assert prints the values
of all subexpressions when it fails, so a failure message is usually clear without a debugger.
The first test#
A test is test "name" { … } next to the code or in the tests/ directory. Run it with oxl test (for one file: oxl test file.oxl).
fn clamp(x: i32, lo: i32, hi: i32) -> i32 pure => if x < lo { lo } else if x > hi { hi } else { x }
test "clamp keeps values inside the range" {
assert_eq(clamp(5, 0, 10), 5)
assert_eq(clamp(-3, 0, 10), 0)
assert_eq(clamp(99, 0, 10), 10)
}
test "table-driven" { // table-driven: data plus a loop
let cases = [(1, 0, 3, 1), (-5, 0, 3, 0), (7, 0, 3, 3)]
for (x, lo, hi, want) in cases {
assert_eq(clamp(x, lo, hi), want)
}
}
test fuzz "clamp stays in range" (x: i32, lo: i32) { // fuzzing: the runtime generates the arguments
let hi = lo.saturating_add(10)
let c = clamp(x, lo, hi)
assert c >= lo && c <= hi
}Output of oxl test:
running 2 tests
test main::clamp keeps values inside the range ... ok
test main::table-driven ... ok
test result: ok. 2 passed; 0 failed; 0 skippedWalkthrough#
assert_eq(a, b)prints both sides on a mismatch;assert cond,assert x is T,assert_ne,assert_fails(expr, ErrorType)andassert_snapshot(&value)are the other assertions (§18.2).- A table-driven test is an ordinary list of tuples and a
forloop: no special syntax is needed. Add a row to the table and you get a new case. test fuzz "…" (x: i32, lo: i32)is fuzzing: the runtime generates the test parameters (respecting refined types), and a failing input is saved for a rerun (oxl test --kind fuzz, guide).- The body of a test may use
?- an error fails the test and prints the error.
Fixtures, mocks and simulation#
fixture name() -> T { …; yield value; …cleanup }andtest "…" with name as x { … }- set up and tear down resources.mock Service { method(pattern) => answer once, then … }replaces a service,Service.calls(.method)records the calls.test sim "…" seeds 1..=300is a deterministic simulation with a virtual clock and network: an hour passes instantly, and a failingseedis reproduced withoxl test --seed N.
Examples of all three are in the tour of testing and in the recipes.
Common mistake: parameters on an ordinary test#
Parameters are allowed only on test fuzz. An ordinary test with a parameter is the error test-decl:
test "sum" (x: i64) {
assert x + 0 == x
}error
test-decl
Replace test "sum" with test fuzz "sum".
Remember#
testis a language declaration;oxl testruns tests in parallel.- Table-driven tests are a loop over a list; fuzzing is
test fuzzwith parameters. - Keep logic
pure: it is the easiest to test (step 8).
Next: concurrency.