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_with to nail down which error you expect; bare t.throws accepts any throw and can mask bugs.
  • Run tests on every commit. The earlier you find a regression, the cheaper it is to fix.