SCUA has a built-in test runner. You write test functions in
*_test.scua files, assert what you expect, and run
scua test. It reports pass/fail and exits non-zero on
failure, so it fits straight into CI.
#A first test
Put your tests in a file whose name ends in _test.scua,
and write each test as a function named test_*:
-- inventory_test.scua
fn add_item(bag, item)
bag.push(item)
return bag
end
fn test_add_one()
let bag = add_item([], "sword")
assert_eq(len(bag), 1)
end
fn test_add_two()
let bag = add_item(add_item([], "sword"), "shield")
assert_eq(bag[1], "shield")
end
Run the tests from the directory that holds them:
$ scua test
PASS ./inventory_test.scua test_add_one
PASS ./inventory_test.scua test_add_two
2 passed, 0 failed (1 file)
scua test finds every *_test.scua file
under the current directory (or a directory you name:
scua test tests/). Each test_* function runs
on its own, so one failing test doesn't stop the others.
#Assertions
The runner doesn't need a special assertion library; the
assert family is built in.
assert(cond, msg?)faults withmsgunlesscondis truthy.assert_eq(got, want)faults unless the two values are equal, and the message shows both.assert_ne(a, b)faults if the two values are equal.assert_near(got, want, eps)faults unlessgotandwantare withinepsof each other. Use it for floats, which aren't exact.
A failing comparison tells you what it saw:
fn test_math()
assert_eq(2 + 2, 5)
end
$ scua test
FAIL ./math_test.scua test_math assert_eq failed: expected 5, got 4
0 passed, 1 failed (1 file)
#How a test fails
A test fails when it raises a fault that nothing catches: a failed
assert, a runtime fault like an out-of-range index, or an
explicit error(...). Anything that would stop a normal
program stops the test and marks it failed, with the located message. A
test that runs to the end without faulting passes.
Because each test_* runs in isolation, a fault in one is
reported and the rest keep going. If a file has a syntax, type, or
compile error, the whole file is reported as an error and its tests
count as failures.
#Files without test functions
If a *_test.scua file has no test_*
functions, the whole file is treated as a single test: it passes if it
runs without an uncaught fault. This is handy for a quick check or a
script-style test.
-- smoke_test.scua
assert(1 < 2, "basic sanity")
assert_near(0.1 + 0.2, 0.3, 0.001)
#A note on actors
The runner shines for synchronous logic: pure functions, data
transforms, validation, anything you can call and assert on directly.
Testing a partition's message handling is harder, because a partition
processes its messages after your test function has already returned, so
you can't tell an actor and assert on the result in the
same test. For now, test the plain functions your handlers call (keep
the logic out of the handler where you can), and check end-to-end actor
behaviour by running a small program and looking at its output.