Using the Bnlang Test Framework
A test file in Bnlang is just a normal script. You import test, register some cases with t.test(name, fn), and finish with t.run(). This page covers the practical bits — how to organize tests, how to run them, and how to wire them into CI.
Running One Test File
bnl tests/math_spec.bnl
t.run() prints a summary and exits with code 1 if any test failed — that's the only signal CI needs.
Running a Whole Folder
There's no built-in glob, but your shell already has one. Run every spec file with:
# POSIX shells
for f in tests/*.bnl; do bnl "$f" || exit 1; done
# PowerShell
foreach ($f in Get-ChildItem tests\*.bnl) { bnl $f.FullName; if ($LASTEXITCODE) { exit $LASTEXITCODE } }
Layout Conventions
A typical project keeps tests next to the code they exercise:
my-app/
bnl.json
bnl.lock
src/
parser.bnl
formatter.bnl
tests/
parser_spec.bnl
formatter_spec.bnl
A Multi-Case Spec
// file: tests/parser_spec.bnl
import "test" as t;
import "parser" as parser;
t.test("parses integers", function () {
t.equal(parser.parse("42"), 42);
});
t.test("parses negatives", function () {
t.equal(parser.parse("-3"), -3);
});
t.test("rejects garbage", function () {
t.throws_with(function () { parser.parse("not a number"); }, "expected number");
});
t.run();
CI Integration
Because each test file exits with 0 on success and 1 on any failure, plugging into CI is trivial. Example GitHub Actions step:
- name: Run Bnlang tests
shell: bash
run: |
for f in tests/*.bnl; do
bnl "$f" || exit 1
done
Best Practices
- Keep test files short and focused. One subject, one spec file.
- Make test names describe the behavior —
"parses negatives"beats"test 3". - Use
t.throws_withto nail down which error you expect; baret.throwsaccepts any throw and can mask bugs. - Run tests on every commit. The earlier you find a regression, the cheaper it is to fix.