Enam perintah manajer berkas sudah berjalan, tetapi mencobanya satu per satu lewat terminal tidak akan sanggup mengejar perubahan kode — setiap kali formatUkuran diubah, kita harus mengulang seluruh pemeriksaan manual. Bagian ini menutup proyek dengan test otomatis memakai node:test yang sudah terpasang bersama Node.js, tanpa package tambahan. Kontrak yang kita bangun di tiga bagian sebelumnya ternyata mudah diuji: parsing argumen, isi keluaran tiap perintah, pesan galat, dan kode keluar semuanya sudah berbentuk fungsi atau nilai yang bisa dibandikan.

Peta Pengujian #

Test dibagi menurut pendekatannya. Sebagian besar mengimpor fungsi langsung dari src/ karena cepat dan bisa memeriksa nilai kembalian secara presisi; sisanya menjalankan src/cli.js sebagai proses nyata lewat child_process karena kode keluar dan pemisahan stdout/stderr hanya terlihat dari luar proses.

Berkas testFokusPendekatan
tests/argumen.test.jsValidasi perintah, jumlah argumen, dan opsiUnit — fungsi murni
tests/format.test.jsformatUkuran dan formatWaktuUnit — fungsi murni
tests/galat.test.jsKode keluar kedua kelas galat dan penerjemahan ubahGalatUnit — fungsi murni
tests/baca.test.jsdaftar, baca, info termasuk jalur galatnyaUnit — folder uji sementara
tests/tulis.test.jstulis, salin, hapus termasuk penolakan --yaUnit — folder uji sementara
tests/cli.test.jsKode keluar 0, 1, dan 2 dari luar prosesIntegrasi — spawnSync

Folder Uji Sementara #

Test perintah berkas butuh berkas sungguhan untuk dibaca dan ditulis, tetapi tidak boleh meninggalkan bekas di folder proyek. Solusinya satu pembantu kecil pada tests/bantu-uji.js: setiap test memanggil folderUji(t) untuk mendapat folder kosong yang unik di folder sementara sistem, lalu t.after mencatat pembersihannya.

js tests/bantu-uji.js
export async function folderUji(t) {
  const folder = await mkdtemp(join(tmpdir(), 'manajer-uji-'));
  t.after(() => rm(folder, { recursive: true, force: true }));
  return folder;
}

mkdtemp menempelkan enam karakter acak di belakang awalan manajer-uji-, jadi test yang berjalan bersamaan tidak akan berbagi folder. Callback pada t.after dijamin dijalankan test runner setelah test selesai — berhasil maupun gagal — dengan opsi recursive dan force yang siap menghapus folder beserta isinya walau tidak ada isinya sama sekali. Pola ini membuat seluruh suite aman dijalankan berulang kali tanpa menumpuk sampah.

Menguji Fungsi Perintah #

Dengan folder uji di tangan, test perintah baca cukup menyiapkan berkas kecil lalu membandikan keluaran fungsi. Dua test berikut berasal dari tests/baca.test.js:

js tests/baca.test.js
test('baca memberi nomor baris pada setiap baris', async (t) => {
  const folder = await folderUji(t);
  const berkas = join(folder, 'catatan.txt');
  await writeFile(berkas, 'baris satu\nbaris dua\n');

  assert.equal(await baca(berkas), '  1 | baris satu\n  2 | baris dua');
});
js tests/baca.test.js
test('baca meneruskan galat ENOENT untuk berkas hilang', async (t) => {
  const folder = await folderUji(t);
  await assert.rejects(
    () => baca(join(folder, 'hilang.txt')),
    (galat) => galat.code === 'ENOENT',
  );
});

Test pertama memakai assert.equal untuk membandingkan seluruh teks keluaran — nomor baris, pemisah |, sampai isi barisnya. Test kedua memakai assert.rejects dengan fungsi pemeriksa: promise ditolak dan fungsi itu mengembalikan true hanya bila galatnya membawa code yang kita harapkan. Pola fungsi pemeriksa ini dipakai juga untuk memastikan GalatPemakaian selalu membawa kodeKeluar 1 dan GalatBerkas membawa 2, sehingga kontrak kode keluar ikut terkunci oleh test.

Menguji Kontrak Lewat Proses Nyata #

Hal-hal seperti process.exitCode dan pesan yang ditulis ke stderr hanya bisa dilihat dari luar proses. Test integrasi pada tests/cli.test.js menjalankan CLI dengan spawnSync:

js tests/cli.test.js
const CLI = fileURLToPath(new URL('../src/cli.js', import.meta.url));

function jalankanCLI(args, cwd) {
  return spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8', cwd });
}
js tests/cli.test.js
test('tanpa perintah keluar 1 dengan pesan di stderr', () => {
  const hasil = jalankanCLI([]);
  assert.equal(hasil.status, 1);
  assert.equal(hasil.stdout, '');
  assert.match(hasil.stderr, /perintah tidak diberikan/);
});

process.execPath adalah path binary Node.js yang sedang berjalan, jadi test tidak bergantung pada bagaimana Node dipanggil. Opsi cwd mengarahkan setiap test ke folder ujinya sendiri, sehingga path seperti data/halo.txt merujuk ke folder sementara tersebut. Hasil spawnSync menyimpan status (kode keluar), stdout, dan stderr sebagai nilai biasa yang tinggal diperiksa — tiga fakta inilah yang membedakan test integrasi dari unit test.

Menjalankan Seluruh Suite #

Dari folder proyek, seluruh test dijalankan dengan satu perintah tanpa argumen — Node.js otomatis mencari berkas *.test.js di sekitar folder kerja, termasuk di dalam folder tests/:

bash
node --test

Output yang Dihasilkan:

text
✔ parseArgumen mengenali perintah dan argumen posisi
✔ parseArgumen memisahkan opsi dari argumen posisi
✔ parseArgumen menerima perintah help tanpa argumen
✔ parseArgumen menolak pemanggilan tanpa perintah
✔ parseArgumen menolak perintah yang tidak dikenal
✔ parseArgumen menolak jumlah argumen yang salah
✔ parseArgumen menolak opsi yang tidak dikenal
✔ parseArgumen menerima argumen berawalan strip setelah pemisah
✔ baca memberi nomor baris pada setiap baris
✔ baca menandai berkas kosong
✔ baca menerima berkas tanpa baris baru di akhir
✔ baca menolak path menuju folder
✔ baca meneruskan galat ENOENT untuk berkas hilang
✔ daftar mengurutkan entri, menampilkan ukuran, dan menandai folder
✔ daftar pada folder kosong hanya mencetak kepala dan total
✔ daftar meneruskan galat ENOTDIR untuk path berkas
✔ info menampilkan nama, jenis, dan ukuran berkas
✔ info menampilkan waktu ubah yang dibekukan dengan utimes
✔ info pada folder menampilkan jenis folder tanpa ukuran
✔ help keluar 0 dan mencetak daftar perintah
✔ tanpa perintah keluar 1 dengan pesan di stderr
✔ perintah tidak dikenal keluar 1
✔ argumen kurang keluar 1
✔ tulis lalu baca berkas lewat proses nyata
✔ daftar lewat proses nyata menampilkan berkas baru
✔ hapus menolak tanpa ya lalu berhasil dengan ya
✔ baca berkas hilang keluar 2 dengan pesan di stderr
✔ formatUkuran menampilkan byte di bawah satu kilobyte
✔ formatUkuran menampilkan kilobyte dengan koma desimal
✔ formatUkuran menampilkan megabyte untuk berkas besar
✔ formatWaktu mencetak waktu UTC dengan format stabil
✔ formatWaktu memotong detik dan milidetik
✔ GalatPemakaian membawa kode keluar 1
✔ GalatBerkas membawa kode keluar 2
✔ ubahGalat meneruskan galat milik manajer berkas apa adanya
✔ ubahGalat menerjemahkan galat ENOENT menjadi pesan Indonesia
✔ ubahGalat menerjemahkan galat EISDIR untuk path folder
✔ ubahGalat membungkus galat sistem yang tidak dikenal
✔ tulis membuat berkas beserta folder induknya
✔ tulis menimpa isi lama secara default
✔ tulis dengan opsi lanjut menyambung di akhir berkas
✔ tulis mengembalikan pesan konfirmasi
✔ tulis meneruskan galat EISDIR untuk path folder
✔ salin menyalin isi berkas ke tujuan
✔ salin menolak asal berupa folder
✔ salin menolak tujuan berupa folder
✔ salin meneruskan galat ENOENT untuk asal hilang
✔ hapus menolak tanpa opsi ya
✔ hapus menghapus berkas ketika opsi ya diberikan
✔ hapus menolak folder meskipun opsi ya diberikan
✔ hapus meneruskan galat ENOENT untuk berkas hilang
ℹ tests 51
ℹ suites 0
ℹ pass 51
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0

Ringkasan di ujung output adalah alat baca tercepat: tests 51 mencatat jumlah test yang ditemukan, pass 51 yang berhasil, dan fail 0 yang gagal. Selama fail bernilai 0, seluruh kontrak alat — dari nomor baris baca sampai kode keluar 2 untuk berkas hilang — terbukti masih terpenuhi.

Ketika Test Gagal #

Laporan kegagalan justru bagian paling sering kita baca, jadi mari pelajari bentuknya dengan sengaja membuat satu test yang salah. Berkas kecil berikut mengharapkan 10 / 2 sama dengan 3:

js data/uji-gagal.test.js
import test from 'node:test';
import assert from 'node:assert/strict';

test('pembagian salah tulis', () => {
  assert.equal(10 / 2, 3);
});

Jalankan test runner pada berkas itu:

bash
node --test data/uji-gagal.test.js

Output yang Dihasilkan:

text
✖ pembagian salah tulis
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0

✖ failing tests:

test at data/uji-gagal.test.js:4:1
✖ pembagian salah tulis
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  5 !== 3

    generatedMessage: true,
    code: 'ERR_ASSERTION',
    actual: 5,
    expected: 3,
    operator: 'strictEqual',
    diff: 'simple'
  }

Ada tiga petunjuk yang bisa dibaca langsung: baris test at data/uji-gagal.test.js:4:1 menunjukkan test di baris keempat yang gagal, baris 5 !== 3 memperlihatkan nilai sebenarnya berhadapan dengan nilai yang diharapkan, dan ringkasan fail 1 menjumlahkannya. Dari sini perbaikannya jelas — ganti angka yang diharapkan menjadi 5 (atau perbaiki rumusnya bila memang salah tulis), lalu jalankan kembali suite sampai fail kembali ke 0.

Perapihan Terakhir #

Sebelum proyek dinyatakan selesai, tiga hal sisa perlu dirapikan. Pertama, pastikan pesan help selalu mutakhir: setiap kali menambah perintah atau opsi baru, teks BANTUAN pada src/bantuan.js ikut diperbarui — dokumentasi yang tertinggal dari kode justru menyesatkan. Kedua, jaga kebersihan folder proyek: demo pada buku ini selalu membersihkan folder data buatannya, dan test memakai folder sementara sistem, jadi tidak ada berkas uji yang tertinggal. Ketiga, jalankan node --test setiap selesai mengubah kode, bukan hanya di penghujung proyek.

Pada proyek yang lebih besar, biasanya kita juga menambahkan pintasan test pada package.json — misalnya "scripts": { "test": "node --test" } — sehingga cukup memanggil npm test. Proyek ini memilih tetap memanggil node --test langsung karena package.json-nya memang sengaja minimal, hanya penanda "type": "module" untuk ESM.

Dengan test yang hijau, proyek Manajer Berkas tuntas: kita memiliki CLI utuh dengan kontrak argumen yang tegas, operasi berkas asynchronous, pesan galat berbahasa Indonesia, kode keluar yang konsisten, dan 51 test yang menjaga semuanya. Keterampilan yang sama akan terpakai dalam skala lebih besar pada proyek berikutnya — REST API manajemen tugas — tempat util.parseArgs digantikan routing HTTP dan isi berkas digantikan penyimpanan JSON.