Belajar Desktop Developer - Testing Desktop Apps
Episode 15 of 28

Belajar Desktop Developer - Testing Desktop Apps

Membangun test suite CatatanKu: unit test untuk logika murni, integration test untuk backend & IPC, dan E2E UI testing dengan Playwright yang menjalankan aplikasi nyata seperti pengguna

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

CatatanKu sekarang penuh fitur — dan penuh risiko. Setiap perubahan di storage, IPC, atau UI bisa merusak sesuatu yang tampak tidak berhubungan. Testing adalah jaring pengaman yang membuat kalian berani refactor dan rilis tanpa menunggu laporan bug dari pengguna.

Episode ini membangun piramida testing CatatanKu: unit test di dasar (cepat, banyak), integration test di tengah (backend + IPC), dan E2E UI test di puncak (lambat, sedikit, paling dekat dengan pengalaman nyata). Kita pakai Playwright untuk E2E — tool yang sama untuk web dan Electron/Tauri, sehingga skillnya reusable.

Piramida Testing

100%

Prinsip piramida: makin rendah lapisan, makin cepat dan makin banyak. Jangan membaliknya — suite E2E raksasa akan lambat, rapuh, dan mahal. Kebanyakan bug ditemukan di unit dan integration test.

LapisanContohKecepatanBiaya
Unitreducer, validasi, formatmsrendah
IntegrationDB + IPC + servicedetiksedang
E2Eklik UI nyatamenittinggi

Unit Test: Logika Murni

Menangkap Keputusan, Bukan Implementasi

Unit test terbaik memverifikasi logika murni — fungsi tanpa I/O: reducer (episode 3), validasi (episode 14), format tanggal (episode 11). Contoh testing reducer CatatanKu:

appReducer.test.ts
import { describe, expect, it } from "vitest";
import { appReducer, initialState } from "./app-reducer";
 
describe("appReducer", () => {
  it("menyimpan catatan baru tanpa mengubah state lain", () => {
    const state = appReducer(initialState, {
      type: "note/save",
      note: { id: "1", title: "Belajar testing", content: "..." },
    });
    expect(state.notes).toHaveLength(1);
    expect(state.searchQuery).toBe(initialState.searchQuery);
  });
 
  it("update catatan mengganti entri yang sama, bukan menduplikasi", () => {
    const saved = appReducer(initialState, {
      type: "note/save",
      note: { id: "1", title: "A", content: "" },
    });
    const updated = appReducer(saved, {
      type: "note/save",
      note: { id: "1", title: "A (edit)", content: "" },
    });
    expect(updated.notes).toHaveLength(1);
    expect(updated.notes[0].title).toBe("A (edit)");
  });
});

Reducers yang murni di episode 3 membayar dividen di sini: test tanpa setup database, tanpa mock framework — murni input/output. Test seperti ini adalah mayoritas suite dan berjalan dalam milidetik.

Integration Test: Backend dan IPC

Validasi Perilaku Nyata

Integration test memverifikasi bagian yang berinteraksi dengan dunia nyata: database, command IPC, service sinkronisasi. Contoh testing command create_note Tauri dengan database in-memory:

tests/notes_test.rs
use catatanku_lib::{create_note, list_notes};
 
#[test]
fn create_and_list_notes() {
    // pakai SQLite in-memory untuk test (bukan file produksi)
    let db = test_db();
 
    let id = create_note(&db, "Catatan pertama", "isi").unwrap();
    let notes = list_notes(&db).unwrap();
 
    assert_eq!(notes.len(), 1);
    assert_eq!(notes[0].id, id);
    assert_eq!(notes[0].title, "Catatan pertama");
}
 
#[test]
fn rejects_empty_title() {
    let db = test_db();
    assert!(create_note(&db, "  ", "isi").is_err());
}

Kunci desain yang memungkinkan ini: backend logic dipisahkan dari framework. Jangan menaruh semua logika di dalam closure command Tauri — taruh di modul library (catatanku_lib) yang bisa diuji tanpa meluncurkan aplikasi. Pattern yang sama di Electron: service logic di module murni, ipcMain.handle hanya menjadi pembungkus tipis.

E2E UI Testing dengan Playwright

Menguji Aplikasi Nyata

E2E menjalankan aplikasi lengkap dan berinteraksi seperti pengguna: klik, ketik, tunggu hasil. Playwright mendukung Electron secara native dan Tauri lewat eksekusi binary:

e2e/catatanku.spec.ts
import { test, expect, _electron as electron } from "@playwright/test";
 
test("pengguna bisa menambah dan mencari catatan", async () => {
  const app = await electron.launch({ args: ["dist/main.js"] });
  const window = await app.firstWindow();
 
  await window.getByLabel("Judul catatan").fill("Belajar Playwright");
  await window.getByRole("button", { name: "Tambah" }).click();
 
  await expect(window.getByText("Belajar Playwright")).toBeVisible();
 
  await window.getByLabel("Cari catatan").fill("playwright");
  await expect(window.getByText("Belajar Playwright")).toBeVisible();
 
  await window.getByLabel("Cari catatan").fill("tidak-ada");
  await expect(window.getByText("Belajar Playwright")).toBeHidden();
 
  await app.close();
});

Untuk Tauri, jalankan binary hasil build dan targetkan window-nya:

e2e/tauri.spec.ts
import { test, expect } from "@playwright/test";
import { spawn } from "node:child_process";
 
test("app Tauri terbuka dan merender daftar catatan", async () => {
  const app = spawn("./src-tauri/target/release/catatanku");
  const win = await connectToWebview(); // helper: temukan webview lewat CDP
 
  await expect(win.getByText("CatatanKu")).toBeVisible();
  await win.getByLabel("Cari catatan").fill("rust");
 
  app.kill();
});

E2E adalah test perilaku pengguna: fokus pada label yang terlihat (bukan selector internal), sehingga kalau markup berubah, test tetap valid selama perilakunya sama.

Tip

Satu suite E2E yang menutup alur kritis (buka → tambah → cari → ekspor → update) jauh lebih berharga daripada dua puluh test yang menguji selector internal. Pilih alur yang paling sering dipakai pengguna dan buat mereka bisa dijalankan di CI (episode 17) tanpa kepala yang berkedip.

Menata Test Suite

Pemilihan Tool per Framework

LapisanTauriElectronFlutter
Unitcargo testVitest/Jestflutter test
Integrationcargo test + in-memory DBVitest + real DBflutter test + sqflite_common_ffi
E2EPlaywright + binaryPlaywright (Electron)integration_test + flutter drive

Konvensi yang Konsisten

  • Test database selalu in-memory atau temp dir — jangan menyentuh data pengguna nyata.
  • Satu file per modulapp-reducer.test.ts berdampingan app-reducer.ts.
  • Nama test = harapan perilaku — "menolak judul kosong" lebih informatif daripada "test 1".
  • Jalankan cepat di lokal — developer yang test-nya lambat akan mencuri-curi waktu; suite di bawah 2 menit menjaga kebiasaan.

Penutup

Inti yang harus dibawa pulang:

  • Piramida testing: unit (banyak & cepat) → integration → E2E (sedikit & lambat).
  • Unit test menguji logika murni — reducer dan validasi tanpa I/O.
  • Integration test menguji backend + IPC dengan database in-memory; pisahkan logika dari framework.
  • E2E dengan Playwright menjalankan aplikasi nyata dan menguji perilaku pengguna.
  • Test adalah jaring pengaman refactor — investasi yang dibayar saat rilis (episode 17).

Di episode 16 selanjutnya kita membuat CatatanKu benar-benar cepat: performance desktop — pengelolaan memori, startup time, dan CPU profiling dengan pendekatan ukur-dulu-baru-optimasi. Sampai jumpa di episode 16!

Belajar Desktop Developer - Testing Desktop Apps | Belajar Desktop Developer