Learn/UPSIDE

7 - Next 기반 프로젝트 (개념)

Nithen 2025. 2. 28. 19:18
728x90

Overview

정말 간단한 DApp이라면 이전까지의 Hello, World! 수준으로 충분합니다. 그러나 보다 더 규모 있는 애플리케이션을 개발하기 위해서는 프레임워크 위에서 개발할 필요가 있습니다. 이를 통해 다양한 기능을 쉽고 빠르게 추가할 수 있습니다. 또한 기능 검수를 위해 테스트 라이브러리도 필요합니다. case4-next-ts 브랜치*에는 크게 세 가지의 변화가 있는데,

* 프레임워크를 도입한 시점에 vanilla Node 프로젝트 case3을 다루는 것은 오히려 혼란을 줄 것 같아 넘어갑니다

  1. 웹 프레임워크를 사용합니다. Next.js v15 프레임워크
  2. 단위 테스트를 수행합니다. node:test 모듈 
  3. e2e 테스트를 수행합니다. Playwright 라이브러리

이 밖에도 TypeScript의 사용이나 React와 JSX 문법, Tailwind-CSS 등 다양한 변경이 있지만, 이는 본 포스트에서는 다루지 않고, 반드시 설명이 필요한 부분만 짚고 넘어가도록 하겠습니다.


프레임워크? 모듈? 라이브러리?

본론을 진행하기 전에 Overview에서 언급한 내용을 살짝 다루고 가봅시다. 위 세 가지 용어는 의존성(dependency)에 대한 이야기입니다. 같은 의존성인데 이렇게 구분하는 이유가 무엇일까요? 먼저, 모듈에 대해 알아봅시다.

함수와 모듈

소프트웨어 개발 규모가 커지면 다양한 의미 단위가 필요해집니다. 아래 그림에서 각각의 공구는 함수, 공구 통은 모듈입니다. 일단 우리가 못을 박아야 하는 상황에 드라이버를 사용할 수는 없겠죠? 그래서 상황에 알맞은 공구를 만들고 재사용합니다. 만약 그렇게 공구의 수가 수 천개, 수만 개가 되면 어떨까요? 어지럽게 흩어져있거나, 모든 공구가 한 데 섞여 있으면 찾기 어렵겠죠? 그래서 목적과 사용처에 따라 분류를 해둡니다. 마치 유사한 상황에 사용되는 함수들을 하나의 파일에 모아서 작성하는 것처럼요. 근데 우리 집 안에서만 사용하던 공구를 친구가 빌려달라고 하면 어떨까요? 통 없이 공구만 주려고 하면 왔다 갔다 들고 다니기 어렵겠죠? 그래서 통이 필요합니다. 필요할 때 꺼내 쓰고, 필요 없으면 다시 보관하고, 빌려주기도 편하니까요.

왕초보를 위한 Python: 쉽게 풀어 쓴 기초 문법과 실습 - 5.1 모듈이란

 

그럼 실제로 어떻게 생겼는지 살펴봅시다. node:test 모듈은 자바스크립트 테스트 작성을 지원하는 Node.js의 내장 모듈입니다. 사실 그냥 파일인데요, 이게 모듈로 동작할 수 있는 이유는 export/import 구문으로 불러오는 것이 가능하기 때문입니다. 모듈 이름에 마우스를 hover하고, cmd + 클릭하면 참조로 이동할 수 있습니다. 현재 프로젝트에서는 22.13.5 버전 @types/node네이티브 노드 모듈의 타입 정의로 사용하고 있기 때문에, 타입 정의 파일(*.d.ts)로 이동했습니다. 실제 노드 내부에서 export 하고 있는 구문이 보이나요? 참고로 타입 정의는 타입스크립트와 프로젝트 사이의 인터페이스에 불과합니다. 실제 소스를 보고 싶다면 node/lib/test.js를 참조하세요!

import "node:test" module

라이브러리와 프레임워크

코드의 구조와 포함 관계만을 생각하면, 라이브러리는 공구통을 모아둔 창고이고, 프레임워크는 여러 개의 창고가 있는 공장입니다. 즉, 프레임워크가 가장 넓은 범주의 개념이라는 의미입니다. 더 중요한 것은 의존 관계입니다. 우리가 작성한 코드가 또 다른 코드를 호출하는 일반적인 관계에서 벗어나, 우리가 작성한 코드가 프레임워크가 정의한 규칙에 따라 관리되고 호출될 수 있다는 것이 핵심입니다.

TCP School: React 이해하기 - 라이브러리? 프레임워크?

 

가령, Next의 App Router는 (src)/app 내부에 경로에 따른 라우팅을 기본적으로 지원합니다. Node의 Native HTTP 서버에서 라우팅 경로를 명시했던 것과는 다르죠? 물론 바닐라 Node 프로젝트도 그렇게 구현 가능하지만 기본 값이 다르다는 것이 차이입니다. 그래서 편리하지만, 또 프레임워크의 규칙을 알아야 하기 때문에 학습이 필요하죠. 아래 이미지에서 차이가 느껴지시나요? 자세한 설명은 공식 문서를 참조하세요!

vanilla Node.js vs Next.js

pnpm test

현재 프로젝트는 두 가지 종류의 테스트를 진행합니다. 첫 번째는 node:http 모듈 기반의 단위 테스트, 나머지는 playwright 라이브러리 기반의 엔드-투-엔드 테스트입니다. 모종의 로직이나 상호작용에 따른 사이드 이펙트를 알맞게 구현했는지 확인할 때 단위 테스트를 활용하고, 그 결과 사용자에게 예상한 결과가 렌더링되는지 확인할 때 엔드-투-엔드 테스트를 활용하게 됩니다.

  • 말이 어려울 수 있으니 라면을 끓이는 과정으로 예시를 들어볼까요? 조리기구가 준비되어 있는지, 버너에 불이 들어오는지, 물 양은 적절한지 등 각 과정 또는 구성요소를 점검하는 것은 단위 테스트, 그래서 기대했던대로 라면이 완성되었는지 결과를 점검하는 것은 엔드-투-엔드 테스트입니다.

 

"테스트 작성할 시간에 원하는 기능 하나 더 구현하는 게 낫겠다" 고 생각할 수 있습니다. 작은 규모의 애플리케이션에서는 어느 정도 맞는 말일 수 있지만, 규모가 커질수록 테스트는 중요합니다. 그 이유 중 하나는 유지 보수가 편리하기 때문입니다. 가령, 이전에 만들어둔 내부 모듈 A와 외부의 라이브러리의 함수 B를 기반으로 AB를 만들고 있다고 해봅시다.

  • 만약에 외부 라이브러리를 업데이트하는 상황이 온다면 어떨까요? 우선, 대부분의 잘 만들어진 라이브러리는 Backward Compatibility를 최대한 유지하기 위해 노력하고, 불가피하게 버전 간 호환이 깨지는 시점의 업데이트를 Breaking Changes로 명시합니다. 따라서, 제 3자의 코드에 대한 테스트를 작성하는 것은 낭비입니다.
  • 그럼 다시 돌아와서 우리의 관심사는 내부 모듈의 변경이 되겠네요. 만약 우리가 A와 B를 더해서 AB를 만든다고 해봅시다. 이전에는 두 값 모두 숫자로 반환되어, 예를 들어, 1 + 2 = 3의 결과가 나왔습니다. 그런데 열심히 내부 모듈을 리팩토링하다보니, 사용자에게 3 대신 12라는 결과가 반환되는 오류를 확인했습니다. 내부 모듈의 반환 타입이 정수에서 문자열로 변경된 문제였던 것이죠.
    • 이 시점에 테스트 없이는 결과 출력 시점에서부터 역으로 거슬러 올라가며, 변경 사항이 개입되는 시점을 차례대로 검증해야 합니다. 매우 번거롭고 손이 가는 일인데, 심지어 변경이 자주 발생하면 그 수에 비례해서 생산성이 저하되죠.
    • 심지어 확인 방법에 따라서는 버그를 찾기 어려울 수도 있습니다. 예를 들어, 콘솔 출력으로 디버깅을 하는 개발자라면, 숫자 1이나 문자열 "1"을 출력할 때, 두 경우가 모두 동일하게 출력될 수 있습니다.

다소 억지스러워 보일 수 있지만, 어쨌든 잘 정의된 테스트는 내부 모듈의 변경이 가져오는 파생 효과를 식별하고, 소프트웨어를 변화에 견고하게 만드는데 큰 도움을 줍니다. 명령 한 번이면 우리가 의도한대로 소프트웨어가 동작한다는 확신을 얻을 수 있다는 것이 가장 큰 장점이죠. TypeScript의 사용 또한 큰 연관이 있습니다. 우리가 정의한 타입 또는 인터페이스에 맞지 않는 값이 활용되었다는 사실을 빌드 이전에 알 수 있으니까요. 

pnpm test


마치며

이번 시간에는 모듈, 라이브러리, 그리고 프레임워크의 개념과 테스트의 중요성에 대해 학습했습니다. 함수를 포함한.. 모듈을 포함한.. 라이브러리를 포함한.. 프레임워크! 그리고 프레임워크을 사용하면 호출의 주체가 역전되고, 내장 기능을 통해 편리한 개발이 가능하지만, 반대로 규칙을 학습할 필요가 있습니다. 또 테스트를 활용하면 변화에 견고한 소프트웨어를 작성할 수 있다는 사실을 명심합시다!

 

다음 시간부터는 본격적으로 프로젝트의 구성 요소를 살펴보며, 이전 우리의 Hello, World! 앱이 어떻게 변경되었는지 다루겠습니다. 🤗

 

728x90