이번 편의 결과물: coding-test/00-setup/에 프로그래머스·백준·LeetCode 세 제출 형식으로 같은 로직(두 정수의 합)을 각각 구현하고 node --test로 전부 통과시킵니다. · 다루는 개념: 표준입출력(node:readline), 세 플랫폼의 제출 형식 차이, coding-test/ 저장소 구조, node --test
이 편에서 만드는 파일
coding-test/
├── package.json (+)
└── 00-setup/
├── programmers/
│ ├── solution.js (+)
│ └── solution.test.js (+)
├── baekjoon/
│ ├── solution.js (+)
│ └── solution.test.js (+)
└── leetcode/
├── solution.js (+)
└── solution.test.js (+)개념 정리
온라인 저지 세 곳, 세 가지 제출 형식
같은 문제라도 플랫폼마다 무엇을 작성하고 입출력을 누가 처리하는지가 다릅니다.
| 플랫폼 | 작성 대상 | 입력 | 출력 |
|---|---|---|---|
| 프로그래머스 | function solution(...) 하나만 완성 | 채점 시스템이 함수 매개변수로 전달 | return 값 |
| 백준 | 전체 프로그램 | 표준입력(stdin)을 직접 읽음 | console.log로 표준출력(stdout) |
| LeetCode | class Solution 안의 메서드 | 채점 시스템이 메서드 매개변수로 전달 | 메서드 return 값 |
프로그래머스와 LeetCode는 입출력을 채점 시스템이 대신 처리하므로 알고리즘 로직만 작성합니다. 백준은 입력을 직접 읽고 출력까지 직접 찍어야 합니다. 세 형식 모두 핵심 로직 자체는 같은 알고리즘이라는 점이 이번 편의 요점입니다.
node:readline로 표준입력 읽기
백준 형식은 node:readline으로 한 줄씩 입력을 받습니다.
import readline from 'node:readline';
const rl = readline.createInterface({ input: process.stdin });
const lines = [];
rl.on('line', (line) => lines.push(line));
rl.on('close', () => {
// lines 배열에 표준입력의 각 줄이 문자열로 담긴다
});line 이벤트는 입력 줄마다 발생하고, 표준입력이 끝나면 close 이벤트가 한 번 발생합니다. 각 줄은 항상 문자열이므로 숫자가 필요하면 Number(...)나 split(' ').map(Number)로 직접 변환합니다.
문제 폴더 규칙(뒤 모든 편의 기준)
coding-test/아래 주제별 폴더(03-arrays-and-strings등)를 두고, 그 안에 문제마다 번호 붙은 하위 폴더를 만듭니다.- 문제 폴더 하나는 항상
solution.js(풀이)와solution.test.js(검증) 두 파일로 구성합니다. solution.js는 프로그래머스 형식을 기본으로export function solution(...)을 작성합니다. 프로그래머스는 채점 시스템이 함수를 직접 호출하므로export가 필요 없지만, 이 저장소는solution.test.js가 같은 함수를 가져다 쓸 수 있도록export를 붙입니다.solution.js첫 줄에는 파일 경로 주석을, 그 아래에는 시간·공간 복잡도 주석을 씁니다(정확한 표기 규칙은 02편에서 정합니다).node --test를coding-test/루트에서 실행하면 하위 폴더를 몇 단계든 뒤져*.test.js파일을 모두 찾아 실행합니다.
package.json 핵심 필드
| 필드 | 의미 |
|---|---|
type | module로 지정해 import/export(ESM) 문법 사용 |
scripts.test | npm test로 node --test를 실행하도록 등록 |
실습
1. 저장소 만들고 package.json 작성
mkdir coding-test
cd coding-test
npm init -y생성된 package.json을 아래 내용으로 바꿉니다.
// coding-test/package.json
{
"name": "coding-test",
"version": "0.1.0",
"description": "온라인 저지 유형별 JavaScript 문제 풀이 저장소",
"type": "module",
"scripts": {
"test": "node --test"
}
}2. 프로그래머스 형식 작성
두 정수 a, b를 받아 합을 반환하는 로직을 세 형식으로 각각 구현합니다. 먼저 프로그래머스 형식입니다.
// coding-test/00-setup/programmers/solution.js
// 시간복잡도: O(1) — 덧셈 한 번만 수행
// 공간복잡도: O(1) — 추가 메모리를 쓰지 않음
export function solution(a, b) {
return a + b;
}// coding-test/00-setup/programmers/solution.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { solution } from './solution.js';
test('두 정수를 더한다', () => {
assert.strictEqual(solution(1, 2), 3);
});
test('음수를 더해도 정확하다', () => {
assert.strictEqual(solution(-5, 5), 0);
});3. 백준 형식 작성
백준은 한 줄에 공백으로 구분된 두 정수를 표준입력으로 받고 합을 표준출력으로 찍습니다. 핵심 로직은 그대로 solution 함수로 분리해 테스트할 수 있게 유지합니다.
// coding-test/00-setup/baekjoon/solution.js
// 시간복잡도: O(1) — 덧셈 한 번만 수행
// 공간복잡도: O(1) — 추가 메모리를 쓰지 않음
import readline from 'node:readline';
import { pathToFileURL } from 'node:url';
export function solution(a, b) {
return a + b;
}
function main() {
const rl = readline.createInterface({ input: process.stdin });
const lines = [];
rl.on('line', (line) => lines.push(line));
rl.on('close', () => {
const [a, b] = lines[0].split(' ').map(Number);
console.log(solution(a, b));
});
}
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
main();
}main은 이 파일을 node solution.js로 직접 실행할 때만 동작합니다. solution.test.js가 이 파일을 import할 때는 main이 실행되지 않아, 테스트 중에 표준입력을 기다리며 멈추는 일이 없습니다. process.argv[1]은 명령행에 입력한 그대로(상대 경로일 수도 있음)라서 import.meta.url과 문자열로 직접 비교하면 어긋날 수 있습니다. pathToFileURL로 절대 경로 URL로 바꿔 비교해야 운영체제와 무관하게 정확히 동작합니다.
// coding-test/00-setup/baekjoon/solution.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { solution } from './solution.js';
test('두 정수를 더한다', () => {
assert.strictEqual(solution(1, 2), 3);
});
test('0을 더해도 원래 값을 유지한다', () => {
assert.strictEqual(solution(7, 0), 7);
});4. LeetCode 형식 작성
LeetCode는 class Solution 안의 메서드를 완성하는 형식입니다.
// coding-test/00-setup/leetcode/solution.js
// 시간복잡도: O(1) — 덧셈 한 번만 수행
// 공간복잡도: O(1) — 추가 메모리를 쓰지 않음
export class Solution {
getSum(a, b) {
return a + b;
}
}// coding-test/00-setup/leetcode/solution.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { Solution } from './solution.js';
test('두 정수를 더한다', () => {
const solver = new Solution();
assert.strictEqual(solver.getSum(1, 2), 3);
});
test('큰 수도 정확히 더한다', () => {
const solver = new Solution();
assert.strictEqual(solver.getSum(1000, 2000), 3000);
});5. 전체 테스트 실행
node --test확인
- 콘솔에
programmers,baekjoon,leetcode세 그룹의 테스트가 모두 나열되고# pass 6,# fail 0이 출력되면 성공입니다. - 백준 형식이 실제 표준입력을 받는지 직접 확인합니다.
echo 1 2 | node 00-setup/baekjoon/solution.js터미널에 3이 출력되면 성공입니다(Windows PowerShell에서는 echo "1 2" | node 00-setup/baekjoon/solution.js).
직접 해보기
- 같은 방식으로 “두 정수의 차”를 구하는 로직을 프로그래머스·백준·LeetCode 세 형식으로 각각
00-setup/아래 새 폴더에 만들어봅니다. - 백준 형식
solution.js에서pathToFileURL비교 조건문을 지우면 어떤 문제가 생기는지node --test를 실행해 확인해봅니다.
정답 보기(2번)
조건문을 지우면 solution.test.js가 solution.js를 import하는 순간 main()이 즉시 실행되어 readline이 표준입력을 기다리며 테스트가 끝나지 않고 멈춥니다. 파일을 직접 실행할 때만 main()이 돌게 하는 조건문이 반드시 필요합니다.
자주 하는 실수
| 증상 | 원인 | 고치는 법 |
|---|---|---|
node --test 실행이 끝나지 않고 멈춤 | 백준 형식 파일에서 main()을 조건 없이 바로 호출함 | pathToFileURL 비교문으로 직접 실행할 때만 main() 호출 |
| Windows에서 직접 실행해도 아무 출력이 없음 | import.meta.url을 process.argv[1]과 문자열로 직접 비교함(경로 형식 불일치) | pathToFileURL(process.argv[1]).href로 변환 후 비교 |
| 프로그래머스 형식 테스트에서 solution이 정의되지 않음 오류 | solution.js에 export를 안 붙임 | export function solution(...)으로 선언 |
echo 1 2 | node solution.js 실행 후 출력이 없음 | 입력 줄을 공백 기준으로 안 나눠 숫자 변환에 실패 | split(' ').map(Number)로 정확히 분리했는지 확인 |