"Electron 앱도 테스트 없이는 리팩토링이 두렵다" — 메인 프로세스 로직, 렌더러 UI, IPC 통합까지 계층별 테스트 전략이 필요합니다.


테스트 계층

PLAINTEXT
┌─────────────────────────────┐
│      E2E 테스트              │  ← Playwright for Electron
│   (실제 앱 실행)             │
├─────────────────────────────┤
│    통합 테스트               │  ← IPC 핸들러 + 모듈 연동
├─────────────────────────────┤
│    유닛 테스트               │  ← 순수 함수, 유틸리티
│   (Vitest / Jest)           │
└─────────────────────────────┘

유닛 테스트 (Vitest)

BASH
npm install vitest --save-dev
JAVASCRIPT
// utils/parser.js
function parseDeepLink(url) {
  try {
    const parsed = new URL(url);
    return {
      action: parsed.hostname,
      params: Object.fromEntries(parsed.searchParams),
    };
  } catch {
    return null;
  }
}

module.exports = { parseDeepLink };
JAVASCRIPT
// utils/parser.test.js
import { describe, it, expect } from 'vitest';
import { parseDeepLink } from './parser';

describe('parseDeepLink', () => {
  it('올바른 딥링크를 파싱한다', () => {
    const result = parseDeepLink('myapp://open?file=test.txt');
    expect(result).toEqual({
      action: 'open',
      params: { file: 'test.txt' },
    });
  });

  it('잘못된 URL은 null을 반환한다', () => {
    expect(parseDeepLink('not-a-url')).toBeNull();
  });
});

E2E 테스트 (Playwright)

BASH
npm install @playwright/test electron --save-dev
JAVASCRIPT
// e2e/app.spec.js
const { test, expect, _electron: electron } = require('@playwright/test');

let electronApp;
let page;

test.beforeAll(async () => {
  electronApp = await electron.launch({
    args: ['.'],
  });
  page = await electronApp.firstWindow();
  await page.waitForLoadState('domcontentloaded');
});

test.afterAll(async () => {
  await electronApp.close();
});

test('앱이 정상적으로 실행된다', async () => {
  const title = await page.title();
  expect(title).toBe('내 앱');
});

test('버전 정보가 표시된다', async () => {
  const version = await page.locator('#version').textContent();
  expect(version).toMatch(/\d+\.\d+\.\d+/);
});

test('파일 열기 다이얼로그가 동작한다', async () => {
  // IPC 호출을 모킹
  await electronApp.evaluate(async ({ dialog }) => {
    dialog.showOpenDialog = async () => ({
      canceled: false,
      filePaths: ['/tmp/test.txt'],
    });
  });

  await page.click('#open-file-btn');
  const filePath = await page.locator('#current-file').textContent();
  expect(filePath).toContain('test.txt');
});

test('창 최소화/최대화가 동작한다', async () => {
  const win = await electronApp.firstWindow();

  // 최대화
  await electronApp.evaluate(({ BrowserWindow }) => {
    BrowserWindow.getAllWindows()[0].maximize();
  });

  const isMaximized = await electronApp.evaluate(({ BrowserWindow }) => {
    return BrowserWindow.getAllWindows()[0].isMaximized();
  });
  expect(isMaximized).toBe(true);
});

IPC 핸들러 테스트

JAVASCRIPT
// 메인 프로세스 로직을 분리하여 테스트 가능하게
// handlers/notes.js
class NoteHandlers {
  constructor(db) {
    this.db = db;
  }

  async getAll() {
    return this.db.prepare('SELECT * FROM notes').all();
  }

  async create(title, content) {
    const result = this.db.prepare(
      'INSERT INTO notes (title, content) VALUES (?, ?)'
    ).run(title, content);
    return { id: result.lastInsertRowid, title, content };
  }
}

module.exports = { NoteHandlers };
JAVASCRIPT
// handlers/notes.test.js
import { describe, it, expect, beforeEach } from 'vitest';
import Database from 'better-sqlite3';
import { NoteHandlers } from './notes';

describe('NoteHandlers', () => {
  let db;
  let handlers;

  beforeEach(() => {
    db = new Database(':memory:');
    db.exec('CREATE TABLE notes (id INTEGER PRIMARY KEY, title TEXT, content TEXT)');
    handlers = new NoteHandlers(db);
  });

  it('노트를 생성할 수 있다', async () => {
    const note = await handlers.create('테스트', '내용');
    expect(note.title).toBe('테스트');
    expect(note.id).toBeDefined();
  });

  it('모든 노트를 조회할 수 있다', async () => {
    await handlers.create('노트1', '내용1');
    await handlers.create('노트2', '내용2');
    const notes = await handlers.getAll();
    expect(notes).toHaveLength(2);
  });
});

면접 포인트 정리

  • 유닛 테스트: 순수 함수/유틸리티를 Vitest로 검증
  • E2E 테스트: Playwright의 Electron 지원으로 실제 앱 실행 후 검증
  • IPC 핸들러 로직을 분리하면 유닛 테스트가 가능해짐
  • Electron 모듈 의존성이 있는 코드는 모킹이나 E2E로 테스트

테스트를 설정했으면, 다음은 오프스크린 렌더링을 알아봅시다.

댓글 로딩 중...