Zum Inhalt springen
aviral gupta

// B5.4 · ca. 32 Min. · Einstieg

node --test und der Watch-Modus

Nach dieser Lektion wählen Sie, welche Tests node --test ausführt, lesen seine Ausgabe und seinen Exit-Code, markieren Tests und lassen sie beim Bearbeiten erneut laufen.

Lektion 4 von 5 in B5 Ereignisse, Eingaben und erste Tests

Danach können Sie

  • Die richtigen Testdateien ausführen: die Standardmuster, Dateinamen und Globs in Anführungszeichen
  • Die Ausgabe lesen und wählen: spec, tap und dot, die Zusammenfassung, der Exit-Code und --test-name-pattern
  • Tests mit skip, todo und only markieren, mit --watch erneut ausführen und die Befehle als npm-Skripte ablegen
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus B2.1: package.json hat "scripts": {"test": "node --test"}. Welche Befehle führen dieses Skript aus? Wählen Sie alle zutreffenden.

    Wählen Sie alle zutreffenden aus.

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es voraus, bevor Sie weiterlesen. Ein Ordner enthält diese fünf Dateien mit je einem Test: main.test.js, prices.spec.js, test/check.js, tests.js und notes_test.js. Wie viele Tests führt node --test ohne Dateinamen aus?

  3. Üben · Aufgabe 3 von 7

    Ergänzen Sie den Reporter, der ein Zeichen pro Test ausgibt: einen Punkt für jeden bestandenen Test und X für jeden fehlgeschlagenen.

    node --test --test-reporter=____
    node --test --test-reporter=
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Befehl zu, was er für eine Datei mit den drei Tests 3.50 is 350 cents, 0.29 is 29 cents und a word is not a price ausgibt.

  5. Üben · Aufgabe 5 von 7

    node --test führt diese Datei aus. toCents('3,50') wirft einen RangeError, und toCents('-1') würde ebenfalls einen werfen. Was sagen die Zeilen pass und fail der Zusammenfassung, und wie lautet der Exit-Code?

    import {test} from 'node:test';
    import assert from 'node:assert/strict';
    import {toCents} from './main.js';
    
    test('3.50 is 350 cents', () => {
      assert.equal(toCents('3.50'), 350, "toCents('3.50') should be 350");
    });
    
    test.skip('negative prices', () => {
      assert.equal(toCents('-1'), -100, "toCents('-1') should be -100");
    });
    
    test.todo('prices with a comma', () => {
      assert.equal(toCents('3,50'), 350, "toCents('3,50') should be 350");
    });
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Ein Test dieser Datei hat {only: true}. Sie starten node --test ohne weiteres Flag. Welche Tests laufen?

    import {test} from 'node:test';
    import assert from 'node:assert/strict';
    import {toCents} from './main.js';
    
    test('3.50 is 350 cents', () => {
      assert.equal(toCents('3.50'), 350, "toCents('3.50') should be 350");
    });
    
    test('0.29 is 29 cents', {only: true}, () => {
      assert.equal(toCents('0.29'), 29, "toCents('0.29') should be 29");
    });
    
    test('a word is not a price', () => {
      assert.throws(() => toCents('abc'), RangeError, "toCents('abc') should throw a RangeError");
    });
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Nehmen Sie main.js und main.test.js aus B5.3 (toCents und seine Tests). Tragen Sie in package.json "test": "node --test" und "test:watch": "node --test --watch" ein. Starten Sie npm test, dann npm test -- --test-reporter=dot und npm test -- --test-name-pattern=cents. Starten Sie npm run test:watch, ändern Sie in main.js Math.round in Math.floor und speichern Sie; lesen Sie den Fehlschlag, ändern Sie es zurück, speichern Sie erneut und beenden Sie mit Strg+C.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Eine Testdatei, vier Arten, sie auszuführen

prices.test.js hat fünf Tests: drei gewöhnliche, einen übersprungenen und einen TODO-Test, der noch keine Funktion hat. main.js startet node --test viermal als Kindprozess und gibt aus, was jeder Lauf bis zu seiner ersten Leerzeile zeigt, ohne die Dauern, die sich bei jedem Lauf ändern. Die Läufe: der Standard-Reporter spec, ein Namensmuster, der Reporter dot und der Reporter tap für einen Test.

main.js

import {spawnSync} from 'node:child_process';

// Runs node with these arguments and prints what the terminal shows, up to the
// first blank line, without durations such as (1.23ms) and a few summary lines.
function run(args) {
  console.log('$ node ' + args.join(' '));
  const {status, stdout} = spawnSync(process.execPath, args, {encoding: 'utf8'});
  const shown = stdout.split('\n\n')[0].replace(/ \(\d+(\.\d+)?ms\)/g, '');
  console.log(shown.split('\n').filter((line) => !/suites|cancelled|duration_ms/.test(line)).join('\n').trimEnd());
  console.log('exit code ' + status + '\n');
}

run(['--test']);
run(['--test', '--test-name-pattern=cents']);
run(['--test', '--test-reporter=dot']);
run(['--test', '--test-reporter=tap', '--test-name-pattern=word']);

prices.js

// Turns a price like '3.50' into whole cents.
export function toCents(text) {
  if (!/^\d+(\.\d{1,2})?$/.test(text)) throw new RangeError('not a price: ' + text);
  return Math.round(Number(text) * 100);
}

prices.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {toCents} from './prices.js';

test('3.50 is 350 cents', () => {
  assert.equal(toCents('3.50'), 350, "toCents('3.50') should be 350");
});

test('0.29 is 29 cents', () => {
  assert.equal(toCents('0.29'), 29, "toCents('0.29') should be 29");
});

test.skip('negative prices', () => {
  assert.equal(toCents('-1'), -100, "toCents('-1') should be -100");
});

test.todo('prices with a comma');

test('a word is not a price', () => {
  assert.throws(() => toCents('abc'), RangeError, "toCents('abc') should throw a RangeError");
});

Ausführen mit

node main.js

Ausgabe

$ node --test
✔ 3.50 is 350 cents
✔ 0.29 is 29 cents
﹣ negative prices # SKIP
✔ prices with a comma # TODO
✔ a word is not a price
ℹ tests 5
ℹ pass 3
ℹ fail 0
ℹ skipped 1
ℹ todo 1
exit code 0

$ node --test --test-name-pattern=cents
✔ 3.50 is 350 cents
✔ 0.29 is 29 cents
ℹ tests 2
ℹ pass 2
ℹ fail 0
ℹ skipped 0
ℹ todo 0
exit code 0

$ node --test --test-reporter=dot
.....
exit code 0

$ node --test --test-reporter=tap --test-name-pattern=word
TAP version 13
# Subtest: a word is not a price
ok 1 - a word is not a price
  ---
  type: 'test'
  ...
1..1
# tests 1
# pass 1
# fail 0
# skipped 0
# todo 0
exit code 0
  • node --test fand prices.test.js an seinem Namen; main.js und prices.js sind keine Testdateien.
  • Der übersprungene und der TODO-Test werden getrennt von pass und fail gezählt.
  • Das Namensmuster führte zwei Tests aus und ließ die anderen drei in der Ausgabe weg.
  • describe und it, Hooks, Mocks und Coverage sind weitere Funktionen von node:test; die Stufe Intermediate behandelt sie.

Übungen

Übung 1 von 2

Welche Dateien sind Testdateien?

Schreiben Sie isTestFile(path) für Pfade wie 'src/prices.test.js', mit / zwischen den Ordnern. Die Funktion gibt true zurück, wenn node --test ohne Dateinamen die Datei ausführen würde. Die Endung muss js, cjs, mjs, ts, cts oder mts sein. Dann muss der Name davor test sein, mit test- beginnen oder auf .test, -test oder _test enden; oder einer der Ordner heißt genau test. main.test.js und test/unit/f.js zählen also, latest.js und contest/x.js nicht.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Zerlegen Sie den Pfad: const parts = path.split('/'); der Dateiname ist parts.at(-1), die Ordner sind parts.slice(0, -1).

  2. Hinweis 2

    Prüfen Sie zuerst die Endung mit /^(.+)\.(js|cjs|mjs|ts|cts|mts)$/; die erste Gruppe ist der Name vor der Endung.

  3. Hinweis 3

    Dann den Namen: === 'test', startsWith('test-') oder /[.\-_]test$/ für die drei Endungen. latest endet auf test, aber ohne Trennzeichen davor.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

// True when node --test, run without file names, would run this file.
// path uses / between folders, such as 'src/prices.test.js'.
export function isTestFile(path) {
  const parts = path.split('/');
  const match = /^(.+)\.(js|cjs|mjs|ts|cts|mts)$/.exec(parts.at(-1));
  if (!match) return false;
  if (parts.slice(0, -1).includes('test')) return true; // anywhere inside a folder named test
  const stem = match[1];
  return stem === 'test' || stem.startsWith('test-') || /[.\-_]test$/.test(stem);
}

for (const path of ['main.test.js', 'b-test.js', 'test/e.js', 'main.js', 'prices.spec.js']) {
  console.log(path, isTestFile(path));
}
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

// True when node --test, run without file names, would run this file.
// path uses / between folders, such as 'src/prices.test.js'.
export function isTestFile(path) {
  return path.endsWith('.test.js');
}

for (const path of ['main.test.js', 'b-test.js', 'test/e.js', 'main.js', 'prices.spec.js']) {
  console.log(path, isTestFile(path));
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {isTestFile} from './main.js';

test('Namen, die auf .test, -test oder _test enden, zählen', () => {
  for (const path of ['main.test.js', 'src/prices.test.mjs', 'b-test.js', 'c_test.cjs', 'lib/g.test.ts', 'lib/my-test.ts']) {
    assert.equal(isTestFile(path), true, `isTestFile('${path}') sollte true sein`);
  }
});

test('test.js und Namen, die mit test- beginnen, zählen', () => {
  for (const path of ['test.js', 'src/test.mjs', 'test-d.js']) {
    assert.equal(isTestFile(path), true, `isTestFile('${path}') sollte true sein`);
  }
});

test('jedes Skript in einem Ordner namens test zählt', () => {
  for (const path of ['test/e.js', 'test/unit/f.js']) {
    assert.equal(isTestFile(path), true, `isTestFile('${path}') sollte true sein`);
  }
});

test('andere Namen zählen nicht', () => {
  for (const path of ['main.js', 'prices.spec.js', 'tests.js', 'latest.js', 'testing.js', 'contest/x.js']) {
    assert.equal(isTestFile(path), false, `isTestFile('${path}') sollte false sein`);
  }
});

test('nur Skript-Endungen zählen, auch in einem Ordner namens test', () => {
  assert.equal(isTestFile('test/data.json'), false, "isTestFile('test/data.json') sollte false sein");
  assert.equal(isTestFile('main.test.txt'), false, "isTestFile('main.test.txt') sollte false sein");
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 2 von 2

Zählen wie die Zusammenfassung

Schreiben Sie summarize(results). Jedes Ergebnis ist {name, ok}, dazu skip: true oder todo: true, wenn der Test so markiert war. Geben Sie {tests, pass, fail, skipped, todo, exitCode} zurück. Ein übersprungener Test zählt nur unter skipped, ein TODO-Test nur unter todo, ob er bestand oder nicht. Die übrigen zählen unter pass oder fail. exitCode ist 1, wenn fail größer als 0 ist, sonst 0. tests zählt jedes Ergebnis.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Zählen Sie zuerst skipped und todo mit filter: results.filter((r) => r.skip).length.

  2. Hinweis 2

    Die gewöhnlichen Tests sind die ohne Markierung: results.filter((r) => !r.skip && !r.todo). Teilen Sie sie nach ok auf.

  3. Hinweis 3

    exitCode hängt nur von den gewöhnlichen Fehlschlägen ab: fail > 0 ? 1 : 0.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

// Counts results the way node --test does in its summary.
// Each result is {name, ok}, with skip: true or todo: true when the test was marked so.
export function summarize(results) {
  const skipped = results.filter((r) => r.skip).length;
  const todo = results.filter((r) => r.todo && !r.skip).length;
  const plain = results.filter((r) => !r.skip && !r.todo);
  const pass = plain.filter((r) => r.ok).length;
  const fail = plain.length - pass;
  return {tests: results.length, pass, fail, skipped, todo, exitCode: fail > 0 ? 1 : 0};
}

console.log(summarize([
  {name: '3.50 is 350 cents', ok: true},
  {name: 'negative prices', ok: true, skip: true},
  {name: 'prices with a comma', ok: false, todo: true}
]));
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

// Counts results the way node --test does in its summary.
// Each result is {name, ok}, with skip: true or todo: true when the test was marked so.
export function summarize(results) {
  const pass = results.filter((r) => r.ok).length;
  return {tests: results.length, pass, fail: results.length - pass, skipped: 0, todo: 0, exitCode: 0};
}

console.log(summarize([
  {name: '3.50 is 350 cents', ok: true},
  {name: 'negative prices', ok: true, skip: true},
  {name: 'prices with a comma', ok: false, todo: true}
]));

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {summarize} from './main.js';

test('gewöhnliche Tests zählen als pass oder fail', () => {
  const got = summarize([{name: 'a', ok: true}, {name: 'b', ok: false}, {name: 'c', ok: true}]);
  assert.deepEqual(got, {tests: 3, pass: 2, fail: 1, skipped: 0, todo: 0, exitCode: 1}, `summarize ergab ${JSON.stringify(got)}`);
});

test('übersprungene Tests zählen nur als skipped', () => {
  const got = summarize([{name: 'a', ok: true}, {name: 'b', ok: true, skip: true}]);
  assert.deepEqual(got, {tests: 2, pass: 1, fail: 0, skipped: 1, todo: 0, exitCode: 0}, `summarize ergab ${JSON.stringify(got)}`);
});

test('ein fehlschlagender TODO-Test lässt den Lauf nicht fehlschlagen', () => {
  const got = summarize([{name: 'a', ok: true}, {name: 'b', ok: false, todo: true}, {name: 'c', ok: true, todo: true}]);
  assert.deepEqual(got, {tests: 3, pass: 1, fail: 0, skipped: 0, todo: 2, exitCode: 0}, `summarize ergab ${JSON.stringify(got)}`);
});

test('keine Ergebnisse: alles 0, Exit-Code 0', () => {
  const got = summarize([]);
  assert.deepEqual(got, {tests: 0, pass: 0, fail: 0, skipped: 0, todo: 0, exitCode: 0}, `summarize([]) ergab ${JSON.stringify(got)}`);
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Häufige Fehler

Die Optionen hinter die Funktion setzen

import {test} from 'node:test';
import assert from 'node:assert/strict';

function toCents(text) {
  if (!/^\d+(\.\d{1,2})?$/.test(text)) throw new RangeError('not a price: ' + text);
  return Math.round(Number(text) * 100);
}

test('negative prices', () => {
  assert.equal(toCents('-1'), -100, "toCents('-1') should be -100");
}, {skip: true});

Was Node.js ausgibt

RangeError: not a price: -1

Warum, und die Lösung

Die Reihenfolge ist test(name, options, fn). Mit {skip: true} hinter der Funktion ignoriert node:test die Optionen: Der Test läuft, toCents('-1') wirft, und der Test schlägt fehl. Setzen Sie die Optionen zwischen Namen und Funktion, test('negative prices', {skip: true}, () => { … }), oder schreiben Sie test.skip('negative prices', () => { … }), was dasselbe bedeutet.

t.skip() aufrufen, ohne t zu übernehmen

import {test} from 'node:test';

test('prices with a comma', () => {
  t.skip('commas come later');
});

Was Node.js ausgibt

ReferenceError: t is not defined

Warum, und die Lösung

Die Beispiele der Dokumentation schreiben test('…', (t) => { t.skip(); }): t ist der Testkontext, der erste Parameter der Testfunktion. Kopieren Sie den Aufruf ohne den Parameter, ist t ein unbekannter Name, und der Test schlägt fehl, statt übersprungen zu werden. Um einen ganzen Test zu überspringen, braucht test.skip('prices with a comma', () => { … }) gar kein t.

describe und it verwenden, ohne sie zu importieren

import assert from 'node:assert/strict';

describe('toCents', () => {
  it('3.50 is 350 cents', () => {
    assert.equal(Math.round(3.5 * 100), 350, '3.50 should be 350 cents');
  });
});

Was Node.js ausgibt

ReferenceError: describe is not defined

Warum, und die Lösung

Andere Testwerkzeuge stellen describe und it als globale Namen bereit; in Node.js kommen sie aus node:test, wie test. Schreiben Sie import {describe, it} from 'node:test'; oder bleiben Sie bei test(), wie diese Stufe es tut. Die Stufe Intermediate behandelt describe, it und Hooks wie beforeEach.

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Welche Dateien node --test ausführt

node --test ohne Dateinamen durchsucht den Ordner und seine Unterordner. Standardmäßig führt es Dateien mit Namen wie name.test.js, name-test.js, name_test.js, test-name.js oder test.js aus und jede Datei in einem Ordner namens test; .cjs und .mjs zählen auch, ebenso .ts, .cts und .mts. main.js, prices.spec.js und tests.js zählen nicht. Passt nichts, führt es 0 Tests aus und endet mit Exit-Code 0. Nennen Sie Dateien, laufen sie unabhängig vom Namen: node --test prices.spec.js. Ein Glob wie "**/*.spec.js" geht auch; setzen Sie ihn in doppelte Anführungszeichen, damit die Shell ihn nicht vorher auflöst. Ein Name ohne passende Datei gibt Could not find aus und endet mit Exit-Code 1.

Die Ausgabe lesen, einen Reporter wählen

Der Standard-Reporter spec gibt ✔ oder ✖ mit dem Namen jedes Tests aus, dann ℹ-Zeilen, die tests, suites, pass, fail, cancelled, skipped und todo zählen, und die Dauer. Nach einem Fehlschlag listet er die fehlgeschlagenen Tests mit ihren Fehlern auf, und der Exit-Code ist 1; sonst ist er 0. --test-reporter=tap gibt das TAP-Format aus, TAP version 13 und ok 1 - name, das andere Werkzeuge lesen; --test-reporter=dot gibt ein Zeichen pro Test aus, . oder X. Die Dokumentation warnt, dass sich die genaue Ausgabe zwischen Versionen ändern kann; werten Sie sie also nicht maschinell aus. --test-name-pattern=cents führt nur die Tests aus, deren Name zu diesem regulären Ausdruck passt; die anderen fehlen in der Ausgabe.

skip, todo, only, watch und ein Skript

test.skip(name, fn) führt fn nicht aus und zeigt ﹣ name # SKIP. test.todo(name, fn) führt fn aus, aber ein Fehlschlag erscheint als ⚠ name # TODO und ändert den Exit-Code nicht. {only: true} in den Optionen eines Tests heißt „nur diesen ausführen“, und zwar nur mit --test-only; ohne das Flag gibt Node.js einen Hinweis aus und führt alle Tests aus. node --test --watch läuft weiter und führt die Tests erneut aus, wenn sich eine Testdatei oder ein importiertes Modul ändert. Die v24-Dokumentation markiert das als Stability 1, Experimental, während node --watch für Programme stabil ist. Legen Sie die Befehle in package.json ab, "test": "node --test" und "test:watch": "node --test --watch"; npm test -- --test-name-pattern=cents reicht ein Flag weiter.

Quellen

Zuletzt geprüft am 4. Oktober 2026