Node 스크립트로 기기테스트 자동화하기

이은지·2023년 1월 29일
2
post-thumbnail

🚨 문제 정의

정확히 말하면 기기테스트가 아니라, 기기테스트를 위한 사전 작업의 자동화다.

기존에 기기테스트를 위해 해야했던 일련의 작업은 다음과 같다.

  1. 로컬 ip값을 알아낸다. ipconfig getfiaddr en0
  2. 서버의 allowOrigin에 로컬 ip값을 추가한다.
  3. 클라이언트의 API 요청 baseUrl에 로컬 ip값을 반영한다.

🚒 해결 방법

Node 스크립트를 활용한다. 이때 Node 스크립트란 node [파일명] 명령어로 실행할 수 있는 자바스크립트 파일을 의미한다.

1️⃣ 로컬 ip값 알아내기

우선 로컬ip값을 알아내야 했다. 로컬ip값을 알아내는 쉘 명령어를 알고 있었기 때문에, 자바스크립트 파일에서 쉘 명령어를 실행하는 법을 찾아내면 됐다.

쉘 명령어를 실행하기 위해, NodeJS에서 제공하는 child_process를 사용했다.

const { exec } = require('child_process');
exec('ipconfig getifaddr en0', (error, stdout, stderr) => {
  const ip = stdout.trimEnd();
});

2️⃣ 로컬 ip값을 서버와 클라이언트에 삽입하기

  • 서버의 allowOrigin이 정의되어 있는 파일
  • 클라이언트의 API 요청 baseUrl이 정의되어 있는 파일

이 두 가지 모두 자바스크립트 파일이다. 한 자바스크립트 파일에서 다른 자바스크립트 파일을 수정할 수 있는 방법이 있을까?

첫 시도

자바스크립트 파일을 직접 수정하지 말고, node 환경변수 및 env 파일을 활용해보자.

로컬 ip값을 클라이언트의 .env.development.local, 서버의 .env.local 에 작성하고, 각 자바스크립트 파일에서 env파일에 정의된 환경변수를 사용하도록 한다. 로컬ip값을 파일에 작성하기 위해 쉘 명령어 “echo”를 사용한다.

문제점

클라이언트의 경우 env파일에 다른 값이 존재하지 않았다. 따라서 스크립트를 실행할 때마다 새로운 env파일을 생성하면 된다. 반면 서버의 .env.local 파일에는 이미 다른 변수들이 존재했다. 따라서 환경변수를 추가하기 위해서는 기존의 env파일을 수정해야 했다.

“echo”를 사용하면 파일의 맨 마지막에 내용을 추가할 수 있다. 하지만 스크립트를 실행할 때마다 파일 마지막에 새로운 값이 추가된다는 문제가 있다.

// 이런 식으로 계속 늘어난다.
CLIENT_LOCAL_IP = http://… ;
CLIENT_LOCAL_IP = http://… ;
CLIENT_LOCAL_IP = http://… ;

➡️ env파일이 아닌 다른 방법을 통해 로컬ip값을 삽입해야 한다.

두번째 시도

두번째 시도 때에는, 여러 고민을 병렬적으로 진행했다.

1. package.json에 정의된 scripts를 프로젝트 폴더 외부에서 실행하는 법
2. 서버에 로컬ip값을 삽입할 수 있는 다른 방법


우선 스크립트를 통해 하고 싶은 작업을 좀 더 구체화했다.

  1. 로컬 ip값을 알아낸다. ipconfig getfiaddr en0
  2. 서버의 allowOrigin에 로컬ip값을 추가한다.
  3. 🆕 추가된 상태로 서버를 실행한다. npm run dev
  4. 클라이언트의 API 요청 baseUrl에 로컬 ip값을 반영한다.
  5. 🆕 반영된 상태로 클라이언트를 실행한다. npm start

그 다음 질문에 대한 답을 하나씩 찾아갔다.


1. package.json에 정의된 scripts를 프로젝트 폴더 외부에서 실행하는 법

이와 관련해서 내가 알고 있었던 지식은 다음과 같다.

  • npm start는 일종의 alias다. node [npm start와 매핑된 쉘 명령어] 를 쉘에 입력하는 것과 같다.
  • 쉘의 현재 위치에 따라 실행할 수 있는 명령어가 달라진다. 같은 명령어여도 명령어를 실행하는 위치에 따라 다른 작업이 수행될 수 있다. ex) package.json이 없는 폴더에서는 npm start 명령어를 실행할 수 없다. pakage.json 파일이 존재하는 CRA 프로젝트 폴더로 이동 후 같은 명령어를 입력하면 리액트 프로젝트가 실행된다.

반면 새롭게 알고 싶었던 지식은 이거다. 터미널에서 npm start 를 입력하면 어떤 일이 일어나는걸까?

관련해서 내가 세웠던 가설은 다음과 같다.

각 터미널은 하나의 프로세스다. npm start를 입력하면 자식 프로세스를 생성하고, 자식 프로세스에서 해당 명령어와 관련된 작업들이 실행된다.

CTO님과 이 질문에 대해 이야기를 나누었는데, CTO님이 힌트를 주셨다. 바로 'ps'라는 리눅스 명령어였다. 'ps'는 process status로, 프로세스 상태를 볼 수 있는 리눅스 명령어이다.

터미널만 띄워 놓은 상태로 ps를 입력하면 다음과 같은 결과가 뜬다.

각 줄이 하나의 프로세스를 의미한다. PID는 Process ID, PPID는 Parent Process ID이다. 보면 각 zsh쉘이 하나의 프로세스임을 알 수 있다. 나 같은 경우 tmux를 사용 중인데, 위 이미지 상에서 4647이 tmux 프로세스의 PID인 것으로 예상된다. 여러 개의 zshell의 PPID가 동일하게 4647인 것을 확인할 수 있다.

CRA 프로젝트에서 npm start 명령어를 입력한 후 프로세스 상태를 확인해보면 다음과 같다.

npm start 프로세스에서 자식 프로세스를 생성하고, 해당 자식 프로세스가 또다시 자식 프로세스를 생성하는 방식으로 프로그램이 실행되는 것을 볼 수 있다. 위 스크린샷의 세 번째 줄을 보면 CRA에 설치되어 있는 react-scripts 라이브러리의 명령어를 통해 리액트가 실행되는 것을 확인할 수 있다.

결론적으로, 로컬 서버와 리액트를 실행 시키는 package.json의 scripts는 결국 특정 경로에 존재하는 프로그램을 실행하는 명령어였다. 실행하면 (현재 프로세스의) 자식 프로세스가 생성되고, 자식 프로세스 내에서 프로그램이 실행된다. 내가 세웠던 가설이 맞는 셈이다.

따라서 Node 스크립트에서 npm run dev, npm start 를 실행하기 위해서는 로컬ip 알아내는 쉘 명령어를 실행한 것과 동일하게 NodeJS의 child_process를 사용해 npm run dev와 npm start를 실행하면 된다. 해당 Node 스크립트를 실행하는 터미널의 자식 프로세스를 생성하는 셈이 될 것이다.


결론: package.json에 정의된 scripts를 프로젝트 폴더 외부에서 실행하는 법은 다음과 같다.

NodeJS의 child_process 모듈을 활용해 스크립트에서 다음 두 작업을 실행한다.

  1. 프로젝트 폴더 경로로 이동한다.
  2. 명령어를 실행한다.

2. 서버에 로컬ip값을 삽입할 수 있는 다른 방법

서버의 package.json에서 힌트를 얻었다. 서버를 실행할 때 환경변수 값을 주입할 수 있는 것으로 보였다.

그래서 처음에는 npm run dev 대신 뒤에 적혀 있는 명령어를 직접 실행하려고 했다. 그런데 실행이 제대로 안됐다. express와 CRA 자체에서 npm run dev와 뒤의 명령어 사이에 무언가 작업을 처리해주는 것 같았다.

그럼 결국 npm run dev를 실행해야 한다는건데, package.json의 scripts 자체에 인자를 넘겨주는 방법이 있나? 라는 질문을 던지게 됐다. 찾아보니, 있더라!

Sending command line arguments to npm script

단칼에 구글링 성공 ✌🏻 원하는 바가 명확하니 구글링 속도가 엄청 빨라졌다.

❤️✨ 완성된 스크립트

그렇게 완성된 스크립트는 다음과 같다.

// Documents/scripts/index.js
const { exec } = require('child_process');
exec('ipconfig getifaddr en0', (error, stdout, stderr) => {
  const ip = stdout.trimEnd();

  exec('cd ../[서버 폴더명]' && `IP=http://${ip}:3000 npm run dev`);
  exec('cd ../[클라이언트 폴더명]' && `IP=http://${ip}:4001/v2 npm start`);
});

scripts 폴더로 이동한 다음, 터미널에 node .를 입력하면 스크립트가 실행된다.

// 클라이언트의 package.json. 환경변수 값을 주입한다.
"scripts": {
    "start": "PORT=3000 REACT_APP_API_ENDPOINT=$IP react-scripts start",
},

// baseURL을 설정하는 부분. 환경변수를 사용한다. 
export const fetchAxios = axios.create({
  baseURL: process.env.REACT_APP_API_ENDPOINT,
})
// 서버의 package.json
"scripts" : {
	"dev": "CLIENT_LOCAL_IP=$IP NODE_ENV=local tsc-watch --onSuccess \"node dist/index\"",
},

// allowOrigin을 설정하는 부분. 
app.use(cors({
  origin: [
    process.env.CLIENT_LOCAL_IP,
  ],
  credentials: true,
})

CTO님께서 가볍게 던져주신 주제였는데, 하다보니 무척 재밌어서 이만큼이나 해버렸다. 운영체제 수업 때 배웠던 프로세스 개념을 자바스크립트로 다뤄본 게 무척 신기하다 ㅋㅋㅎㅋ 리눅스에 대한 이해도 높아졌다.

고도화되어야 할 부분이 많다. 어떤 경우를 더 고려해야하고, 무엇을 추가해야 하는지 리스트업도 해놨다. 다른 팀원분들 컴퓨터에서도 완벽히 작동해서, Gitlab에 푸시할 수 있는 그 날까지^^! 조금씩 발전시켜 나가야지 🤞🏻

0개의 댓글