반응형

package.json

  • jest : 자바스크립트를 쉽게 테스팅하는 npm 패키지
  • test:cov : 코드가 얼마나 테스팅됐는지 알려줍니다.
{
	"test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
}

 

💡 새로 파일들을 생성하면 파일명 뒤에 .spec.ts 라고 붙은 파일들이 같이 생성되는데 그 파일들이 테스트를 포함한 파일입니다.

 

Nestjs에서는 jest 가. spec.ts 파일들을 찾아볼 수 있도록 설정되어있습니다.

 

  • npm run test:cov
    • 모든 .spec.ts 파일을 찾아서 몇 줄이 테스팅됐는지 알려줍니다.
  • npm run test:watch
    • 모든 테스트 파일들을 찾아서 거기서 무슨 일이 일어나는지 관찰합니다.

 

Unit Testing

  • 서비스에서 분리된 유닛을 테스트한다.
  • 모든 function 을 따로 테스트
    ex) movie.service.ts > getAll() 함수 하나만 테스트하고 싶을 때 사용

e2e Testing (end-to-end)

  • 모든 시스템을 테스팅한다.
    ex) 이 페이지로 가면 특정 페이지가 나와야 하는 경우 사용

Jest

  • 자바스크립트 테스팅 프레임워크

 

 

Unit Test

💡 afterAll() 안에는 데이터베이스를 모두 지우는 function을 넣을 수 있음 beforeEach, afterEach, beforeAll, afterAll 등 많은 훅이 있습니다.

 

간단한 프로젝트를 통해 unit test를 진행해보도록 하겠습니다.

 

movies.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { MoviesService } from './movies.service';

describe('MoviesService', () => { // 테스트를 묘사..?
  let service: MoviesService;

  beforeEach(async () => { // 테스트하기전에 실행
    const module: TestingModule = await Test.createTestingModule({
      providers: [MoviesService],
    }).compile();

    service = module.get<MoviesService>(MoviesService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });
});

맨 하단에 이런 함수를 넣어서 실행해봅니다.

...
it('should be 4', () => {
    expect(2+2).toEqual(4);
  })
...

npm run test:watch

&nbsp;a 를 누른다.

 

Unit Testing

getAll()

  • getAll() 메소드를 호출한 후 result 인스턴스가 배열인지 테스트
describe('getAll', () => {
    it('should return an array', () => {
      const result = service.getAll();
      expect(result).toBeInstanceOf(Array);
    });
  });

npm run test:watch

→ getAll : shold return an array

 

getOne()

  • getOne() 메소드가 잘 호출되는지 테스트
    • 999 id 값을 가진 movie 가 없을 때 에러 메시지를 던지는지 확인
describe('getOne', () => {
    // getOne() 을 테스트 할 때 Movie 가 create 되어 있지 않다면 문제가 생길 수 있음
    it('should return a movie', () => {
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });

      const movie = service.getOne(1);
      expect(movie).toBeDefined();
      expect(movie.id).toEqual(1); // movie id 값이 1이 맞는지 확인
    });

    it('should throw 404 error', () => {
      try{
        service.getOne(999);
      }catch(e){
        expect(e).toBeInstanceOf(NotFoundException);
        expect(e.message).toEqual('Movie with ID 999 not found.');
      }
    })
  });

 

반응형

deleteOne()

  • 먼저 movie 를 생성한다.
describe('deleteOne', () => {
    it('deletes a movie', () => {
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });

      console.log(service.getAll());
    });
  });

생성한 값들을 console log 로 출력

 

 

  • movie 생성 후 전체 movie의 length와 하나의 movie를 delete 한 후의 movie length를 비교한다.
    • afterDelete 가 beforeDelete 보다 작을 것이라고 예상
describe('deleteOne', () => {
    it('deletes a movie', () => {
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });

      const beforeDelete = service.getAll().length;
      service.deleteOne(1);
      const afterDelete = service.getAll().length;
      expect(afterDelete).toBeLessThan(beforeDelete); // afterDelete < beforeDelete
    });
    it('should return a 404', () => {
      try{
        service.deleteOne(999);
      }catch(e){
        expect(e).toBeInstanceOf(NotFoundException);
      }
    })
  });

 

create()

describe('create', () => {
    it('should create a movie', () => {
      const beforeCreate = service.getAll().length
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });
      const afterCreate = service.getAll().length
      console.log(beforeCreate, afterCreate);
      expect(afterCreate).toBeGreaterThan(beforeCreate);
    });
  });

 

update()

describe('update', () => {
    it('should update a movie', () => {
      //const beforeUpdate = service.getAll()
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });

      service.update(1, {
        year: 2025
      });

      //const afterUpdate = service.getAll()
      //console.log(beforeUpdate, afterUpdate)
      const movie = service.getOne(1)
      expect(movie.year).toEqual(2025);
    })
  })

 

describe('update', () => {
    it('should update a movie', () => {
      //const beforeUpdate = service.getAll()
      service.create({
        title: 'Test',
        genres: ['test'],
        year: 2020,
      });

      service.update(1, {
        year: 2025
      });

      //const afterUpdate = service.getAll()
      //console.log(beforeUpdate, afterUpdate)
      const movie = service.getOne(1)
      expect(movie.year).toEqual(2025);
    })

    it('should throw a NotFoundException', () => {
      try{
        service.deleteOne(999);
      }catch(e){
        expect(e).toBeInstanceOf(NotFoundException);
      }
    })
  })

 

npm run test:cov

→ movies.service.ts 파일이 100%로 바뀌면 unit test 성공입니다!

 

반응형

'Backend > Nestjs' 카테고리의 다른 글

[NestJS] JWT AuthGuard/Strategy  (0) 2024.01.04
[NestJS] NestJS 란?  (0) 2022.06.10
[NestJS] E2E Testing  (0) 2022.02.27
[NestJS] Swagger 생성하기  (0) 2022.02.21
[NestJS] Docker 304 undefined 에러  (0) 2022.02.14
반응형

문제

API 배포 후 개발 서버에서 도커 로그 파일 확인 결과 304 undefined 이슈 발생

 
도커 로그 파일 확인 코드

docker logs -f [컨테이너 ID]

 

원인

chrome에서 동적 메소드 실행 시 캐시가 저장되기 때문에 발생한 이슈
 
 

해결

main.ts 코드 수정

// main.ts

const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 동적 요청에 대한 응답을 보낼 때 etag 생성을 하지 않도록 설정 (모든 캐시 저장 X)
app.set('etag', false);

또는

app.use(function (req, res, next) {
    res.header('x-powered-by', 'Blood, sweat, and tears.');
    next();
  });
반응형

'Backend > Nestjs' 카테고리의 다른 글

[NestJS] JWT AuthGuard/Strategy  (0) 2024.01.04
[NestJS] NestJS 란?  (0) 2022.06.10
[NestJS] E2E Testing  (0) 2022.02.27
[NestJS] Swagger 생성하기  (0) 2022.02.21
[NestJS] 유닛 테스트(Unit Testing)  (0) 2022.02.16
반응형

nest.js 프로젝트를 생성해서 docker로 배포해보도록 하겠습니다.

 

nestjs 새 프로젝트 생성 (docker)

nestjs cli 설치

npm install -g @nestjs/cli

새로운 프로젝트 생성

nest new nest-test



Dockerfile 생성

## base image for Node 10-alpine(light weight)
FROM node:10-alpine
WORKDIR /app

## 프로젝트의 모든 파일을 WORKDIR(/app)로 복사한다
COPY . .

## Nest.js project를 build 한다
RUN yarn install
RUN yarn build

## application 실행
EXPOSE 80
CMD ["yarn", "start:dev"]

💡 start ⇒ typescript로 작성되어있는 서버 실행
💡 start:dev ⇒ nodemon을 활용해서 소스코드가 변경될 시 서버를 자동으로 재실행
💡 start:prod ⇒ build 명령어를 통해 컴파일되어 생성된 js 파일을 실행

.dockerignore 파일 생성

node_modules
dist

 

도커 이미지 빌드

docker build -t nest-test .

 

백그라운드에서 실행하기

docker run -d -p [연결할포트번호]:80 [리포지토리]:[태그]


컨테이너 id로 docker log를 확인할 수 있습니다.

반응형

'Backend > Docker' 카테고리의 다른 글

[DOCKER] Docker 이해하고 사용하기 (1)  (0) 2022.02.13
반응형

Docker란?

  • 컨테이너 기반의 오픈소스 가상화 플랫폼
  • 다양한 환경을 컨테이너로 추상화하고 동일한 인터페이스를 제공하여 프로그램 배포 및 관리를 단순하게 한다.

컨테이너란?

  • 컨테이너는 개별 소프트웨어의 실행에 필요한 실행환경을 독립적으로 운용할 수 있도록 기반 환경 또는 다른 실행환경과의 간섭을 막고, 실행의 독립성을 확보해주는 운영체계 수준의 격리 기술입니다.
  • 애플리케이션을 실제 구동 환경으로부터 추상화할 수 있는 논리 패키징 메커니즘을 제공한다.

💡 두 개의 소프트웨어가 동일한 라이브러리를 사용하지만 버전 또는 운영체제가 다를 경우 도커를 이용하여 간단하게 해결할 수 있다.

  • 컨테이너는 이미지를 실행한 상태이고, 추가되거나 변하는 값은 컨테이너에 저장됩니다.

이미지란?

  • 컨테이너 실행에 필요한 파일과 설정값 등을 포함하고 있다.
  • 상태 값을 가지지 않고 변하지 않는다.
  • 같은 이미지에서 여러 개의 컨테이너 생성 가능
  • 컨테이너의 상태가 바뀌거나 삭제되어도 변하지 않는다.

💡 이미지는 컨테이너 정보를 가지고 있기 때문에 용량이 크다. 만약 파일 하나가 추가돼서 이미지가 변경되어야 한다면 굉장히 비효율적이다. → 이런 문제로 layer라는 개념을 사용한다.

docker layer

  • 이미지는 여러개의 읽기 전용 layer로 구성되고, 파일이 추가되거나 수정되면 새로운 layer가 추가된다.
  • web app 이미지에서 web app source 파일을 수정했다면 web app source 만 다운로드하면 된다.

컨테이너 실행 옵션들

docker 명령어

  • 컨테이너 목록 확인 docker ps [option] [컨테이너 id]
  • 컨테이너 중지 docker stop [option] CONTAINER [컨테이너 id]
  • 이미지 목록 확인 docker images [option]
  • 이미지 다운로드 docker pull [option] 이미지명:[tag|@digest]
  • 이미지 삭제 docker rmi [option]

Dockerfile

💡 Dockerfile의 한줄한줄은 레이어 형태로 저장되기 때문어 RUN을 줄이면 레이어가 줄어들고, 캐시도 효율적으로 관리할 수 있다.

  • FROM : 어떤 이미지로부터 이미지를 생성할지 지정해줌
  • RUN : 명령어 실행 지시자
    • && : 여러 명령어를 이어서 실행하기 위한 연산자
    • \ : 명령어를 여러줄에 작성하기 위한 문자
  • WORKDIR : 디렉토리 변경
  • ENV : 컨테이너 실행 환경에 적용되는 환경변수의 기본값을 지정하는 지시자
  • EXPOSE : 가상머신에 오픈할 포트 지정
  • CMD : 컨테이너에서 실행될 명령어 지정
# moniwiki 모니위키 도커파일

FROM ubuntu:14.04

RUN apt-get update &&\\
  apt-get -qq -y install git curl build-essential apache2 php5 libapache2-mod-php5 rcs

WORKDIR /tmp
RUN \\
  curl -L -O <https://github.com/wkpark/moniwiki/archive/v1.2.5p1.tar.gz> &&\\
  tar xf /tmp/v1.2.5p1.tar.gz &&\\
  mv moniwiki-1.2.5p1 /var/www/html/moniwiki &&\\
  chown -R www-data:www-data /var/www/html/moniwiki &&\\
  chmod 777 /var/www/html/moniwiki/data/ /var/www/html/moniwiki/ &&\\
  chmod +x /var/www/html/moniwiki/secure.sh &&\\
  /var/www/html/moniwiki/secure.sh

RUN a2enmod rewrite

ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2

EXPOSE 80

CMD bash -c "source /etc/apache2/envvars && /usr/sbin/apache2 -D FOREGROUND"



[Docker] - [DOCKER] Docker 이해하고 사용하기 (2)

반응형

'Backend > Docker' 카테고리의 다른 글

[DOCKER] Docker 이해하고 사용하기 (2)  (0) 2022.02.13
반응형

git-flow를 사용하여 동료들과 협업을 하면서 느낀 점과 사용방법을 정리해보았습니다.

GIT-FLOW 란?

git branch 전략 중 하나로 Vincent Driessen의 브랜칭 모델을 위한 고수준 저장소 작업을 제공하는 git의 확장입니다.
Vincent Driessen의 브랜칭 모델에는 5개의 branch(master, develop, feature, release, hotfix)가 사용됩니다.

GIT-FLOW를 선택한 이유

레거시 프로젝트 같은 경우는 Forking Workflow 방식을 사용을 했었는데, 각 개발자들마다 원격 저장소를 만들고 pull request를 보내야 합니다.
기준이 명확하고 관리를 잘한다면 좋은 방식이지만, 몇 년 동안 여러 개발자들이 거쳐가면서 정리되지 않은 엄청난 양의 branch들이 생겼습니다.
branch들은 생성한 본인이 관리를 해야 하기 때문에 관리가 어려웠습니다.
그래서 신규 프로젝트들은 모두 git-flow를 적용시켜서 협업을 시작했습니다.

GIT-FLOW의 강점

- 사용이 간편해 쉽게 접근 가능하다.
- branch 관리를 더 깔끔하게 할 수 있다.
- 버전에 따라 관리가 쉽다.
- 배포 및 수정이 간편해 빠르게 배포가 가능하다.

Branch

  • master : 정식으로 배포되는 소스를 관리하는 브랜치
  • develop : 다음 배포를 위해 개발단의 소스를 관리하는 브랜치
  • feature : 새로운 기능 개발을 위한 브랜치
  • release : 성능 개선, 버그 수정 등을 위한 브랜치
  • hotfix : master를 기반으로 긴급 패치 작업을 위한 브랜치



GIT-FLOW 설치

- macOS

brew install git-flow-avh

- Linux

apt-get install git-flow

- Windows

wget -q -O - --no-check-certificate https://raw.github.com/petervanderdoes/gitflow-avh/develop/contrib/gitflow-installer.sh install stable | bash

* git-flow의 설치를 위해서는 wget과 util-linux가 필요합니다.

GIT-FLOW 사용방법

초기화

git flow를 사용하려면 프로젝트 설정을 변경하기 위해 초기화 작업이 필요합니다.

git flow init

git flow init 명령으로 초기화를 해주고 브랜치의 명명규칙을 정해줍니다. (변경 가능)

feature 브랜치

새로운 기능 개발을 위한 feature branch를 생성합니다.

git flow feature start <branch name>


개발 완료가 되었다면 다음 명령어를 실행하여 develop에 merge 합니다.

git flow feature finish <branch name>

finish를 하면 develop으로 checkout 후 병합하며, 해당 브랜치는 삭제됩니다.

만약 pull request가 필요하거나 코드 리뷰 또는 해당 브랜치를 확인하기 위해 remote 저장소에 올려야 한다면 다음 명령어를 실행합니다.

git flow feature publish <branch name>

원격 저장소에 브랜치를 push 합니다.

다른 개발자가 push 한 브랜치를 track으로 가져올 수 있습니다.

git flow feature track origin <branch name>



관리가 용이해서 요즘 많이 사용하고 있는 git branch 전략 중 하나인 것 같습니다.
프로젝트에 맞게 한번 사용해보시는 것 추천드립니다!

반응형

+ Recent posts