Testing
Testing
Kōdo has a built-in testing framework. Tests are first-class constructs in the language — you write them alongside your code using the test keyword, and run them with kodoc test.
Writing Tests
A test block uses the test keyword followed by a string literal name and a body:
module math_utils {
meta {
purpose: "Math utilities with tests"
}
fn add(a: Int, b: Int) -> Int {
return a + b
}
fn multiply(a: Int, b: Int) -> Int {
return a * b
}
test "add returns correct sum" {
assert_eq(add(2, 3), 5)
assert_eq(add(-1, 1), 0)
assert_eq(add(0, 0), 0)
}
test "multiply returns correct product" {
assert_eq(multiply(3, 4), 12)
assert_eq(multiply(0, 5), 0)
}
}
Tests live inside the same module as the code they test. Each test block has a descriptive name (a string literal) and a body containing assertions.
Assertions
Kōdo provides these built-in assertion functions:
| Assertion | Signature | Description |
|---|---|---|
assert | assert(Bool) | Fails if the value is false |
assert_true | assert_true(Bool) | Fails if the value is false (alias for assert) |
assert_false | assert_false(Bool) | Fails if the value is true |
assert_eq | assert_eq(T, T) | Fails if left and right are not equal |
assert_ne | assert_ne(T, T) | Fails if left and right are equal |
assert_eq and assert_ne support these types: Int, String, Bool, and Float64. Both operands must be the same type.
Running Tests
Use kodoc test to run tests in a file:
# Run all tests in a file
kodoc test examples/testing.ko
# Run only tests whose name matches a pattern
kodoc test examples/testing.ko --filter "add"
# Output results as JSON (for agent consumption)
kodoc test examples/testing.ko --json
Exit Codes
| Code | Meaning |
|---|---|
0 | All tests passed |
1 | One or more tests failed |
Describe Blocks
Group related tests with describe. Use setup and teardown for shared initialization:
describe "arithmetic" {
setup {
let base: Int = 100
}
teardown {
// cleanup after each test
}
test "addition" {
assert_eq(base + 1, 101)
}
test "subtraction" {
assert_eq(base - 50, 50)
}
}
setupruns before each test in the describe blockteardownruns after each test- Variables from
setupare visible in all tests - Describe blocks can be nested; test names become hierarchical:
"arithmetic > addition"
Skip and Todo
Mark tests to be skipped or as future work:
@skip("waiting for async support")
test "async operation" {
assert(false)
}
@todo("implement when ready")
test "future feature" {
assert(false)
}
Skipped and todo tests are reported separately in the summary and don’t count as failures.
Timeout
Set a time limit for individual tests:
@timeout(5000) // milliseconds
test "long computation" {
// aborts if it takes more than 5 seconds
}
Property-Based Testing
Use @property and forall to test properties with random inputs:
@property(iterations: 100)
test "addition is commutative" {
forall a: Int, b: Int {
assert_eq(a + b, b + a)
}
}
@property(iterations: 50, seed: 42)
test "abs is non-negative" {
forall x: Int {
assert(abs_val(x) >= 0)
}
}
Annotation parameters:
| Parameter | Default | Description |
|---|---|---|
iterations | 100 | Number of random inputs |
seed | 0 (random) | Deterministic seed for reproducibility |
int_min | -1000000 | Min value for Int generation |
int_max | 1000000 | Max value for Int generation |
Supported types in forall: Int, Bool, Float64, String.
When a property test fails, the runtime performs basic shrinking — attempting smaller inputs to find a minimal failing case.
Generating Tests from Contracts
Use kodoc generate-tests to auto-generate test stubs from function contracts:
# Generate tests to a separate file
kodoc generate-tests mymodule.ko
# Append tests inline to the source file
kodoc generate-tests mymodule.ko --inline
# Print generated tests to stdout
kodoc generate-tests mymodule.ko --stdout
Given a function with contracts:
fn safe_divide(a: Int, b: Int) -> Int
requires { b != 0 }
ensures { result >= 0 }
{ ... }
The generator produces:
test "safe_divide: basic call" {
// TODO: call safe_divide() with valid arguments
}
@property(iterations: 100)
test "safe_divide: postcondition holds" {
forall a: Int, b: Int {
// TODO: add precondition guard and postcondition assertion
}
}
Annotations on Tests
Tests support the same annotations as functions — @confidence and @authored_by:
@confidence(0.95)
@authored_by(agent: "claude")
test "critical invariant holds" {
let result: Int = compute_critical_value()
assert_eq(result, 42)
}
This integrates with Kōdo’s agent traceability system. See Agent Traceability for details.
How Test Mode Works
When you run kodoc test, the compiler:
- Parses the file normally, collecting all
testanddescribeblocks. - Flattens describe blocks into individual tests with hierarchical names and injected setup/teardown.
- Desugars each test into a regular function and generates a synthetic
mainthat runs them. - Compiles and runs the resulting program.
Tests have full access to all functions and types defined in the module — no special imports needed.
Known Limitations
- Single file only:
kodoc testcurrently runs tests within a single file. Cross-file test discovery is not yet supported. - Property test types:
forallcurrently supportsInt,Bool,Float64, andString. Collection types (List,Map) in forall bindings are planned. - Shrinking: Basic shrinking only (divide toward zero). Compositional shrinking across multiple variables is planned.
Examples
See these files in the repository for complete working examples:
examples/testing.ko— basic test blocks with assertionsexamples/test_describe.ko— describe blocks with setupexamples/test_property.ko— property-based testingexamples/test_contracts.ko— testing with contracts