프론트 대부분 그리고 백엔드 약간을 맡아 개발하고 있었는데 뭔가 수정할 때마다 잘 되는지 하나하나 postman으로 확인하는 것이 귀찮아졌다. 그래서 말로만 듣던 테스트를 올려보기로 했다.
기술 같은 경우 백엔드는 mysql, prisma ORM, nodejs, express를 사용하고 있다. 그리고 프론트는 nextjs 12 버전을 사용하고 있다.
기본적인 세팅
설치와 설정
jest를 쓰려고 했으나 module 지원이 안되는 문제가 있었다. 그래서 코드가 호환되는 vitest를 사용하기로 했다.
그리고 package.json에 다음과 같이 테스트 스크립트를 추가해준다.
그리고 다음과 같이 vitest 설정 파일을 만들어서 __test__폴더에 들어 있는 .test.js 혹은 .test.ts 확장자의 파일을 테스트하도록 설정해준다.
기본적인 테스트해보기
간단히 함수를 만들고 테스트해보는 걸로 시작해보자. utils/sample.js 파일을 생성하고 다음과 같이 코드를 작성한다. n을 받아서 2의 제곱수들을 2^n까지 넣은 배열을 반환하는 함수이다.
그리고 __test__/sample.test.js 파일을 생성하고 다음과 같이 코드를 작성한다. 간단한 덧셈과 powersOfTwo 함수를 테스트해보자.
그리고 npm test를 실행하면 다음과 같이 테스트가 실행된다. 예상대로 잘 실행되는 걸 볼 수 있다. 당연히 테스트도 성공이다.
테스트용 DB 설정
하지만 이런 JS 함수나 테스트하자고 테스트를 돌리는 건 아니다. 먼저 백엔드에서 DB가 잘 다루어지는지 테스트를 하고 또 궁극적으로는 프론트에서 백엔드와 잘 연결되는지 테스트를 해야 한다. 따라서 먼저 테스트용 DB를 설정해보자.
컨테이너 생성
테스트용 DB로 mysql을 사용하기로 했다. docker로 다음과 같이 mysql 컨테이너를 생성하자. 로컬에서 돌아갈 테스트이므로 DB 이름은 testdb로 설정하고 비밀번호는 testpassword로 아주 간단히 설정했다. 또한 3307포트로 접근할 수 있도록 했다.
테스트 DB 설정
prisma/schema.prisma 파일에는 db url을 설정할 수 있는데 이를 환경 변수를 이용하도록 설정한다.
그리고 .env.test 파일을 생성하고 다음과 같이 DB URL을 설정한다. 프로덕션용 URL은 물론 .env에 들어 있다. 테스트용 DB URL은 .env.test에 설정해준다.
이제 테스트 DB에 필요한 테이블을 생성해보자. prisma migrate 명령어를 사용하면 prisma/migrations 폴더에 마이그레이션 파일이 생성된다. 이 파일을 통해 DB에 테이블을 생성할 수 있다.
이때 오류가 뜰 수 있다. 이는 prisma migrate가 프로젝트의 .env에 있는 DATABASE_URL을 사용하기 때문이다.
따라서 이때만 잠시 .env 파일의 DATABASE_URL을 위에서 만든 .env.test의 것으로 변경하거나, 아니면 package.json의 스크립트에 직접 DATABASE_URL을 넣어주거나 schema.prisma에 직접 DATABASE_URL을 넣어주거나 등등 아무튼 testdb의 URL이 잘 들어가도록 해주면 된다. 바로 다음에 볼 dotenv-cli를 사용할 수도 있겠다.
그리고 테스트 시에 .env.test 파일을 사용하도록 설정해줘야 한다. 먼저 dotenv-cli를 설치해서 스크립트에서 환경 변수 파일을 설정할 수 있도록 하자.
그리고 package.json에 다음과 같이 테스트 스크립트를 수정한다. .env.test 파일을 테스트 시 환경변수로 쓰도록 설정한다.
seed 설정하기
도커 컨테이너로 테스트 DB가 올라갔고 prisma migrate를 통해 테이블도 생성했다. 이제 seed 데이터를 넣어보자. prisma/seed-test.js 파일을 생성하고 다음과 같이 코드를 작성한다. 단순히 모든 테이블의 기존 데이터를 지워 버리는 코드이다.
vitest.config.js 파일을 다음과 같이 수정해서 테스트 전에 seed-test.js 파일을 실행하도록 설정한다. globalSetup에서는 주어진 파일들에서 default export된 함수를 실행한다.
테스트 코드 작성
이제 테스트 코드를 작성해보자. __test__/student.test.js 파일을 생성하고 다음과 같이 코드를 작성한다. 일단 테스트 파일에서 테스트용 데이터베이스에 연결하도록 설정하고 간단한 테스트 케이스를 작성한다. 학생을 만드는 함수는 미리 만들어둔 StudentRepository를 사용했다.
npm run test를 실행하면 다음과 같이 테스트가 실행된다. mysql 컨테이너가 실행되고 있는지 확인하는 것에 주의하자.
도커 컴포즈로 자동화
docker-compose.test.yml 파일을 만들어서 테스트용 DB를 자동으로 생성하도록 설정해보자. 다음과 같이 도커 컴포즈 파일을 작성한다.
이제 다음과 같은 스크립트들을 통해서 테스트를 실행할 수 있다. docker-compose down을 통해 컨테이너를 정리하는 것도 매우 쉬워졌다.
다음과 같이 package.json에 이 명령들을 묶어서 스크립트로 추가한다.
이제 npm run test:docker를 실행하면 테스트용 DB를 생성하고 마이그레이션을 수행하고 테스트를 실행하고 DB를 삭제하는 과정을 자동으로 수행한다.
트러블 슈팅
그런데 문제가 발생한다. 자꾸 다음과 같은 에러가 발생하는 것이다. 직접 명령을 실행하면 이런 에러가 발생하지 않는데 npm run test:docker를 실행하면 이런 에러가 발생한다.
신기한 건 이렇게 에러가 발생하고 명령이 종료된 후 다시 한 번 npm run test:docker를 실행하면 이번에는 에러가 발생하지 않았다. 그리고 다시 성공적으로 명령이 완료되고 docker-compose down까지 실행되고 나서 다시 npm run test:docker를 실행하면 또다시 에러가 발생한다.
따져보니 다음과 같은 상황이었다.
npm run test:docker을 실행한다.
스크립트의 첫 명령인 docker-compose -f docker-compose.test.yml up -d가 실행된다.
테스트를 위한 mysql 컨테이너가 실행된다.
npm run migrate:test가 실행된다.
prisma migrate가 실행되는데, 아직 docker-compose에서 실행하려고 하는 mysql 컨테이너가 완전히 준비되지 않은 상태에서 실행된다.
따라서 서버에 접근할 수 없다는 에러가 발생하고 스크립트가 종료된다. docker-compose down이 실행되기 전에 종료되었으므로 mysql 컨테이너는 계속 실행된다.
다시 npm run test:docker를 실행하면 mysql 컨테이너가 실행되어 있는 상태에서 prisma migrate가 실행되고 이번에는 컨테이너가 준비된 상태에서 실행되어 성공한다.
그리고 테스트가 실행되고 성공적으로 종료된다. 이때 docker-compose down이 실행되어 mysql 컨테이너가 종료된다.
mysql 컨테이너가 종료되었으므로 다시 npm run test:docker를 실행하면 앞선 상황이 반복된다.
이 문제를 해결하기 위해서는 prisma migrate가 실행되기 전에 mysql 컨테이너가 완전히 준비되었는지 확인해야 한다. 이를 위해서 wait-for-it이라는 스크립트를 사용하자.
일단 wait-for-it GitHub 레포지토리에서 wait-for-it.sh 파일을 다운로드 받는다. 그리고 프로젝트 루트에 bin/폴더를 만들고 wait-for-it.sh 파일을 넣는다.
bin/run-test.sh 파일을 만들고 다음과 같이 코드를 작성한다. 이 스크립트는 mysql 컨테이너가 준비되었는지 확인하고 준비되었으면 다음 명령을 실행한다.
그리고 package.json의 test:docker 스크립트를 다음과 같이 수정한다.
이 스크립트를 실행하면 이제는 mysql 컨테이너가 준비된 상태에서 prisma migrate가 실행되어 에러가 발생하지 않는다. 실행이 안될 수도 있는데, 그럴 때는 파일의 실행 권한을 주자.
npm run test:docker를 실행하면 테스트용 DB를 생성하고 마이그레이션을 수행하고 테스트를 실행하고 DB를 삭제하는 과정을 자동으로 수행한다. DB가 실행되고 나서 마이그레이션, 테스트를 실행하는 이 순서를 지키기 위해서 wait-for-it.sh를 사용했다.
테스트 순차 실행 설정
위에서 세팅한 대로 테스트를 작성하고 실행했더니 에러가 많이 발생했다. vitest는 기본적으로 테스트를 병렬로 실행하는데, 실제 도커 DB를 이용해서 테스트를 하다 보니 테스트마다 데이터를 생성하고 삭제하는 과정에서 충돌이 발생했다. 따라서 테스트를 순차적으로 실행하도록 설정해보자.
이는 간단히 vitest 설정 파일에서 fileParallelism을 false로 설정하면 된다. vitest.config.js 파일을 다음과 같이 수정하자.