ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Nest.js] Test Code
    Backend/Nest.js 2023. 11. 25. 18:36
    728x90

    Test Code를 작성해야 하는 이유

    1. 개발 과정 중 예상치 못한 문제를 미리 발견할 수 있는데, 에러를 클라이언트보다 빨리 발견할 수 있습니다.
    2. 작성한 코드가 의도한 대로 작동하는지 검증할 수 있습니다. 테스트 코드를 통해 동작하는 방식과 결과 확인이 가능합니다.
    3. 코드 수정이 필요한 상황에서 유연하고 안정적인 대응할 할 수 있게 해줍니다.
    4. 즉, 테스트 코드는 코드 변경에 대한 사이드 이펙트를 줄이는 예방책이 됩니다. 또한 코드 변경 시, 변경 부분으로 인한 영향도를 쉽게 파악할 수 있습니다.
    5. 리팩토링 시 기능 구현이 동일하게 되었다는 판단을 내릴 수 있습니다.리팩토링은 기존에 잘 동작하는 코드를 변경하여 발전시키는 작업인데, 리팩토링으로 인하여 기존 기능이 동작하지 않으면 굉장한 손해라고 볼 수 있습니다. 왜냐하면 테스트를 하든, 리팩토링을 하든 기능이 잘 동작하는 것이 최우선이기 때문입니다. 따라서 리팩토링을 하는 시간보다 리팩토링 후 해당 기능을 테스트 하는 시간이 더 요구되는 것은 손해인셈입니다. 하지만 테스트 코드가 존재한다면 리팩토링을 마음 편히 할 수 있습니다. 예를 들어, 버전업을 하면서 현재 구현된 기능이 제대로 버전업에서도 동일하게 구현되게 하는 것입니다.
    6. 만약 테스트 코드가 존재하지 않다면, 리팩토링은 굉장히 힘든 작업이 됩니다.
    7. 문서로서의 역할이 가능합니다.
    8. 테스트 코드는 개발자가 작성한 메소드가 어떻게 동작을 시작하고, 어떻게 동작을 했으면, 어떤 결과를 반환했으면 하는지를 작성한 것입니다. 따라서 개발자들이 테스트 코드를 통해서 코드의 동작을 수월하게 이해할 수 있습니다.

     

    테스트의 종류

    테스트는 테스트 대상 범위나 성격에 따라 크게 3가지로 구분됩니다.

    UI Test, Integration Test(통합 테스트), Unit Test(단위 테스트) 등으로 구분됩니다.

    테스트 피라미드 수준 입니다.

    피라미드 형태로 표현된 이유는 테스트 코드를 작성하는 데 필요한 비용이 때문입니다.

    구글 공식 문서에서는 각 테스트 비율을 단위는 유닛은 70%, 통합은 20%, UI은 10%로 구성하는 것이 좋다고 합니다.

    그럼 한 단계씩 살펴보면서 각 구성에 대해서 알아보겠습니다.

     

    Unit Test (단위 테스트)

    단위 테스트(Unit Test)는 하나의 모듈을 기준으로 독립적으로 진행되는 가장 작은 단위의 테스트입니다.

    여기서 모듈은 애플리케이션에서 작동하는 하나의 기능 또는 메소드로 이해할 수 있다. 예를 들어 웹 애플리케이션에서 로그인 메소드에 대한 독립적인 테스트가 1개의 단위 테스트가 될 수 있습니다.

    즉, 단위 테스트는 애플리케이션을 구성하는 하나의 기능, 하나의 함수가 올바르게 동작하는지를 독립적으로 테스트 하는 것으로, 각각의 조건에 대한 유효성을 검증합니다.

    “어떤 기능이 실행되면 어떤 결과가 나온다” 정도로 테스트를 진행합니다.

     

    Integration Test (통합 테스트)

    중형 테스트에 속하는 통합 테스트(Integration Test)는 모듈을 통합하는 과정에서 서로 다른 모듈 혹은 클래스 간 상호작용의 유효성을 검증하는 테스트입니다.

    웹 페이지로부터 API를 호출하여 올바르게 동작하는 지를 확인하는 것입니다.

    일반적으로 애플리케이션은 여러 개의 모듈들로 구성되고, 모듈들끼리 메세지를 주고 받으면서(함수 호출) 기능을 수행합니다. 그렇기에 통합된 모듈들이 올바르게 연계되어 동작하는지 검증이 필요한데, 이러한 목적으로 진행되는 테스트가 통합 테스트입니다.

    또한 통합 테스트는 각 모듈에 대한 설정 또는 테스트를 하기 위해 사전 조건이 필요한 경우가 있기 때문에 단위 테스트보다 테스트 코드를 작성하기가 복잡합니다. 하지만 단위 테스트보다 더 넓은 범위의 종속성까지 테스트함으로써 단위 테스트보다 좀 더 유의미한 테스트가 되는 경우도 많습니다.

     

    UI Test

    대형 테스트로 분류되는 UI 테스트는 실제 사용자들이 사용하는 화면에 대한 테스트를 하여 서비스의 기능이 정상적으로 작동하는지 검증하는 테스트입니다.

    UI 테스트는 실제 앱을 사용하는 사용자의 흐름에 대해 테스트 함으로써 UI 변경 사항으로 인해 발생할 수 있는 문제를 사전에 차단하여 앱의 신뢰도를 높일 수 있습니다.

    하지만 화면과 직접적으로 연관되어있는 테스트이다 보니 실행 시간도 오래 걸리고, 디자인이 변경될 때마다 테스트 코드의 수정이 필요하므로 유지 보수 비용도 큰 편입니다.

     

    단위 테스트(Unit Test)의 중요성

    Mindspace 프로젝트에서는 단위 테스트를 작성했습니다.

    • 코드를 수정하거나 기능을 추가할 때 수시로 빠르게 검증할 수 있습니다.
    • 리팩토링 시에 안정성을 확보할 수 있습니다.
    • 개발 및 테스팅에 대한 시간과 비용을 절감할 수 있습니다.
    • 코드에 대한 문서가 될 수 있습니다.

    단위 테스트는 해당 기능과 메소드만 독립적으로 테스트하기 때문에 어떤 코드를 리팩토링하여도 빠르게 문제 여부를 확인할 수 있고 기능이 똑같이 동작하는지 확인할 수 있습니다.

     

    Jest를 이용한 테스트 코드 작성

    Nestjs에서는 Javascript 테스트 프레임워크인 jest를 기본 테스트 프레임워크로 지원하고 있습니다.

    Jest를 사용할 때 장점 중 하나는 다른 라이브러리 설치 없이 바로 mock 기능을 지원한다는 점인데, Mocking이란 단위 테스트를 작성할 때 해당 코드가 의존하는 부분을 가짜(mock)으로 대체하는 기법을 말합니다. Mocking을 이용하면 구체적으로 구현해야 하는 실제 객체 사용보다 훨씬 빠르고, 동일한 결과를 내는 테스트를 작성 가능합니다.

    CommentService의 메서드 mock 처리 코드는 다음과 같습니다.

    providers: [
      {
        provide: CommentService,
        useValue: {
          createComment: jest.fn(),
          getCommentsByBoardId: jest.fn(),
          updateComment: jest.fn(),
          deleteComment: jest.fn(),
        },
      },
    ]

    댓글 조회, 작성, 수정, 생성 중 작성에 대한 테스크 코드를 살펴보겠습니다.

    댓글 생성 기능(createComment)은 게시판 ID, 사용자 ID, 댓글 내용, 부모 댓글 ID를 입력 받아 새로운 댓글을 생성합니다. 이 기능을 테스트하기 위해 **jest.spyOn**을 사용하여 createComment 메서드를 목(mock) 처리합니다. 이렇게 하면 실제 데이터베이스나 외부 시스템에 의존하지 않고 메서드가 예상대로 작동하는지 확인할 수 있습니다.

    jest.spyOn(service, 'createComment').mockResolvedValue(mockCommentResponse);

    먼저, 정상적으로 댓글이 생성되는 경우를 테스트합니다. 여기서는 목(mock) 데이터를 사용하여 댓글 생성 시 예상되는 결과를 설정하고, createComment 메서드를 호출하여 실제 결과와 일치하는지 검증합니다.

    describe('createComment', () => {
        it('should return comments', async () => {
        const boardId = 1;
        const userId = '1';
        const createCommentDto = {
          content: '댓글 작성',
        };
        const parentId = 1;
        const mockCommentResponse: CommentResponseDto = {
          id: 1,
          userNickname: 'user1',
          content: '댓글 작성',
          updatedAt: new Date('2023-11-19T07:25:29.688Z'),
        };
    
        jest
          .spyOn(service, 'createComment')
          .mockResolvedValue(mockCommentResponse);
    
        const result = await service.createComment(
          boardId,
          userId,
          createCommentDto,
          parentId,
        );
    
        expect(service.createComment).toHaveBeenCalledWith(
          boardId,
          userId,
          createCommentDto,
          parentId,
        );
        expect(result).toEqual(mockCommentResponse);
      });
    });

    댓글 기능에서 발생할 수 있는 다양한 예외 상황들도 테스트합니다. 예를 들어, 존재하지 않는 사용자 ID(UserNotFoundException), 존재하지 않는 게시판 ID(BoardNotFoundException), 유효하지 않은 부모 댓글 ID(NotFoundExceptionBadRequestException) 등의 경우를 검증합니다.

    /** 게시글을 찾지 못할 때 예외처리 */
    it('should throw a BoardNotFoundException', () => {
      try {
        const createCommentDto = {
          content: '댓글 작성',
        };
            // boardId가 999인 데이터를 생성
        service.createComment(999, '1', createCommentDto, 1); 
      } catch (e) {
        expect(e).toBeInstanceOf(BoardNotFoundException); 
      }
    });
    
    /** 부모 댓글을 찾지 못할 때 예외처리 */
    it('should throw a NotFoundException', () => {
      try {
        const createCommentDto = {
          content: '댓글 작성',
        };
            // parentId가 999인 데이터를 생성
        service.createComment(1, '1', createCommentDto, 999); 
      } catch (e) {
        expect(e).toBeInstanceOf(NotFoundException);
      }
    });
    
    /** 부모 댓글이 대댓글일 때 예외처리 */
    it('should throw a BadRequestException', () => {
      try {
        const createCommentDto = {
          content: '댓글 작성',
        };
            // parentId가 2 데이터를 생성(대댓글의 대댓글)
        service.createComment(1, '1', createCommentDto, 2);
      } catch (e) {
        expect(e).toBeInstanceOf(BadRequestException);
      }
    });

     


    REF

    https://velog.io/@ecvheo1/Test-Test-Code는-왜-작성해야-하는가

     

    728x90

    'Backend > Nest.js' 카테고리의 다른 글

    [Nest.js] Guards  (0) 2024.04.04
    [Nest.js] TypeORM, Repository Pattern  (0) 2023.10.02
    Nest.js  (0) 2023.09.18
Designed by Tistory.