테스트의 종류
테스트에는 많은 종류가 있다.
- 유닛 테스트(Unit Testing) : 컴포넌트, 함수 단위의 작은 코드를 테스트
- 엔드 투 엔드 테스트(End to End Testing) : 사용자 관점에서 취할만한 행동에 따라 각각의 서비스가 정해진 대로 동작하는지 테스트
- 부하 테스트(Load Testing), 스트레스 테스트(Stress Testing) : 얼마나 많은 동시 접속자를 처리할 수 있는지 테스트 (서버 관점에서의 테스트)
Nest.js에서는 기본적으로 E2E(End To End) 테스트 파일을 만들어주고, 컨트롤러, 서비스 각각을 제너레이터를 사용하여 생성하면 유닛 테스트 파일도 자동으로 만들어준다.
E2E 테스트는 test
폴더의 app.e2e-spec.ts
에 있고, 유닛 테스트는 src
폴더 내부에 .spec.ts
의 파일로 사용한다.
유닛 테스트
기본적으로 describe
it
expect
만 사용해서도 테스트할 수 있고, 원활한 사용을 위해서는 beforeEach
beforeAll
afterEach
afterAll
도 같이 쓴다.
테스트 구조
1 | describe('MoviesService', () => { |
beforeEach에 대해서는 테스트 구조 설명 후 알아보겠다.
먼저 테스트할 서비스를 불러오고, 서비스가 정의된 객체인지 판별한다. 테스트에 통과했다면 이제 서비스에 구현한 각각의 함수(Unit)에 대해 테스트를 수행하면 된다. describe
내부에 describe
를 써서 상위, 하위 개념으로 작성한다.
1 | describe('getAll', () => { |
getAll
메소드는 배열 타입을 반환해야 하는데, 이를 테스트한 것이다. expect
는 말그대로 “기대”이다. expect(a).toEqual(b)
라면 “a가 b와 같기를 기대해” 라는 의미이고, toEqual
, toBeDefined
, toBeGreaterThan
등 expect
에서 사용할 수 있는 다양한 메소드를 활용해서 테스트를 진행한다.
하나 더 예를 들면, 서비스가 404(Not Found)를 뱉어주기를 원한다면 다음과 같이 작성할 수 있다.
1 | it('should return a 404', () => { |
물론 서비스에서도 핸들링을 통해 NotFoundException
을 뱉어야 한다. 이런 저명(?)한 예외 처리는 @nestjs/common
에 왠만하면 다 들어가있어서 별도로 에러 객체를 구현하지 않아도 된다.
beforeEach, beforeAll, afterEach, afterAll
describe
내부에 넣는 하나의 메소드인데, 말 그대로 테스트 전/후로 각각 테스트/전체 테스트에 메소드를 실행하는 것이다.
예를 들어 beforeEach
나 beforeAll
은 테스트할 서비스 객체를 가져오거나 DB 연결을 초기화하는 등의 작업을 수행할 수 있고, afterEach
나 afterAll
로는 테스트 후 DB를 정리하는 등의 작업을 할 수 있다. 대부분의 테스트는 DB와 함께 동작할텐데, 이 때 필요한 동작을 번거로움없이 해결 할 수 있다.
테스트 수행
1 | $ yarn test # npm run test |
기본적으로 test
명령어로 테스트를 실행하고, :watch
를 사용하면 파일이 저장되어 바뀔 때마다 새롭게 테스트를 실행할 수 있다.
E2E 테스트
E2E 테스트도 유닛 테스트와 구조는 다를 게 없다. 테스트 하는 대상의 범위만 다를 뿐이다. 유닛 테스트가 톱니바퀴에 나사가 잘 끼워졌는지, 윤활은 잘 되어있는지 등을 검사한다면 E2E 테스트는 톱니바퀴가 잘 돌아가는 그림 정도를 본다.
테스트 구조
1 | describe('AppController (e2e)', () => { |
Nest가 자동으로 E2E 테스트의 틀은 짜준다. 여기서 채워넣으면 되는데, 주의할 점이 있다. 테스트 전에 실행하는 메소드인 beforeAll
내부를 보면 app
을 다시 생성한다. 따라서 main.ts
에서 별도로 app
에 global pipe를 추가했거나 다른 파이프를 연결한 경우 필요에 따라 다시 app에 미들웨어를 끼워줘야 한다. 이러지 않으면 실제 구동할 때와 테스트할 때 app의 속성이 달라서 당황할 수도 있다.
GET 테스트
/
에 GET 요청이 들어왔을 때의 E2E 테스트를 보자.
1 | it('/ (GET)', () => { |
beforeAll
에서 초기화한 app
을 사용하기 위해 request()
메소드를 사용한다. request(app.getHttpServer())
를 통해 GET, POST 등의 요청을 보낼 수 있다. 그 후 똑같이 expect
를 테스트 목적에 맞게 이어주면 된다.
POST 테스트
POST 요청에 대한 테스트로 하나 더 예를 들자면,
1 | it('POST 201', () => { |
GET과 방식은 유사한데, send
메소드를 통해 Body에 내용을 실어서 보낸다. 그리고 정상적으로 POST되었다는 응답인 201 상태를 expect
에 걸어주면 된다.
1 | it('POST 400', () => { |
Validator에 대한 검증을 위해 성공하면 안되는 경우를 테스트할 때는 위처럼 400 상태를 expect
에 걸어주면 된다.
uri에 대한 테스트 묶기
그리고 일반적으로 한 uri에 포함된 GET, POST, DELETE 등의 테스트들은 한 개의 describe
에 묶어준다.
1 | describe('/movies/:id', () => { |
위 같은 방식으로 묶어주면 테스트 화면에서
이렇게 가독성 좋은 결과를 출력할 수 있다.
테스트 수행
1 | $ yarn test:e2e # npm run test:e2e |
말 그대로 test
에 :e2e
를 붙여주면 E2E 테스트를 수행할 수 있다.
마치며
테스트 커버리지 보기
테스트에 대한 커버리지를 보고 싶다면 다음과 같이 입력하자.
1 | $ yarn test:cov # npm run test:cov |
정상적으로 테스트를 모든 유닛에 대해 적용했다면 100이 나온다.
테스트의 중요성
Nest에서 기본적으로 제공하는 Jest를 활용한 테스트를 알아보았다. 실제 실무에서는 비즈니스 로직에 대한 코드 작성보다 테스트를 위한 코드 작성에 시간을 더 할애하는 경우도 많다고 한다. 그만큼 테스트가 중요하다는 말이다.