ubq 0.0.7(beta)

ubq

The best test tool for coding agents. Their humans like it too.

Tests for TypeScript and JavaScript, on Node, Bun, and Deno.
No build step. No tricky config. No output that nobody reads.

npm install --save-dev ubqtest

Short output

A pass is one line. A failure is one block: the place, the source line, the difference, and the command that runs that one test again.

Tests that cannot pass for the wrong reason

A test with no assertion fails. A promise that nothing waits for fails. Work that goes on after the test ended fails.

Coverage for each test, always on

ubq knows which tests ran each line. After a change, it runs only the tests that the change can affect.

One small API

test, describe, assert, and a few helpers. No globals, no hooks, no .only. Less to remember, and less to get wrong.

Type errors are failures

Each run also runs the type checker of the project. A test suite that passes with code that does not compile is not a pass.

A real browser for front-end code

browser() gives a Playwright page, with Chromium. No DOM copy that differs from the real one, and no driver to choose.

A store of past runs

Failures, history, and coverage are in a local SQLite file. A question about the last run is a command, not a second run.

Made for the loop that an agent runs

An agent changes code, runs tests, reads the output, and decides. It does that many times in one task. Each step of ubq is short, and each one gives the next command.

  1. ubq --changed
    Runs the tests that ran the code you changed. Not the tests of the files near it. The tests that ran those lines.
  2. ubq uncovered --diff
    Lists the code that you changed and that no test runs. This is the code that needs a test now.
  3. ubq mutate --diff
    Makes small changes to the lines you changed, one at a time, and lists each change that no test fails for. It tells you if the new tests are strong.
  4. ubq failures
    Prints the failures of the last run again. Nothing runs.
$ npx ubq --changed
FAIL test/parse.test.ts > parse > circle
  expected values to be equal
  at test/parse.test.ts:9:12
  > assert.equal(shape.kind, 'circle')
  actual:   'square'
  expected: 'circle'
  rerun: ubq test/parse.test.ts:6

fail: 1 failed, 1 passed (run 2)
$ npx ubq test/parse.test.ts:6
pass: 0 failed, 1 passed (run 3)

That is all the output. There is no more to scroll past.

A test file is a plain program

Each name comes from one import. The file runs with ubq, and also with node, bun, or deno run.

// test/parse.test.ts
import { assert, describe, test } from 'ubqtest'
import { parse } from '../src/parse.ts'

describe('parse', () => {
  test('circle', () => {
    const shape = parse('c')
    assert(shape) // narrows away undefined
    assert.equal(shape.kind, 'circle')
  })

  test('unknown input', () => {
    assert.throws(() => parse('?'), /unknown shape/)
  })
})

Wrong forms are type errors that teach

An agent that learned a different test tool writes assert.ok or test.only from habit. In ubq, each of those is a type error, and the message gives the form to use.

Moving from a different tool

ubq convert changes the tests of tap, node:test, vitest, jest, and mocha. It makes the changes that are certain. Where a decision is necessary, it writes a TODO(ubq) comment with the instruction. The steps are in the guide.

Why a different test tool?

Most test tools print for a person at a terminal: progress, colors, and a long report.
An agent pays for each of those lines, and then must find the three that matter.
These are the opinions that ubq has about that.

Output is a cost.
If a line does not help with the next decision, ubq does not print it. The report of a run with no failures is one line.
A green run must mean something.
A test that asserts nothing, or that ends before its work ends, tells you nothing. Other tools let it pass. ubq fails it, and says why.
Coverage is not a report at the end.
It is data that the tool uses on each run: to select tests, to find code with no test, and to answer ubq covers src/parse.ts:12.
A stuck test must say where it is stuck.
A test that goes over its time limit fails, and the report names what it left open and the line that opened it.
The documents are in the package.
ubq guide prints the API and the rules for the installed version. An agent does not need to guess from an older version that it remembers.
Fewer ways to do a thing.
One assertion for equality. One way to group tests. No setting that changes what a test means. Tests from different authors look the same.

Every runtime, Consistent results

Each test file runs in its own process, in parallel, in a random order.
A run with a failure prints its seed.

For agents, and humans who work with them

If you are an agent

Each page of this site is also a markdown file. Start at llms.txt, or read all the user documents in one file: llms-full.txt.

In a project that has the package, use npx ubq guide. That text is correct for the installed version.

If you work with an agent

Put this in AGENTS.md or CLAUDE.md:

Tests use ubq (ubqtest package). Before you write or
run tests, read `npx ubq guide`. Run `npx ubq --changed` after a change, then
`npx ubq uncovered --diff`.

Or write a skill to the project: npx ubq skill