업데이트:

태그: ,

카테고리:

필요성

API서버를 개발하면서 가장 많이 느낀 것은 더미데이터를 어떻게 생성하고 관리할 것인가이다.
만들어져있는 테이블에 더미데이터를 생성하는것은 크게 어렵지 않으나,

  1. 개발 도중 테이블 설계의 변경
  2. 부족한 데이터의 양
  3. 참조조건 관련 칼럼의 추가/삭제가 synchronize되지 않아 스키마 전체를 drop해야한다는 단점
  4. 이에따라 데이터베이스 핸들링에 들어가는 시간의 증가 위와같은 문제점이 있었고, 이와 관련한 문제를 해결해주는 패키지가 없을까하며 찾다보니
    (typeorm-seeding)을 찾게되었다.



TypeOrm-seed

TypeOrm-seed의 최근 업데이트 일자는 3년전이다. TypeOrm버전이 0.3버전 이상이지만, TypeOrm-seed의 의존성이 낮아서 어떤 문제가 발생할지 몰라서 최근에 자주 사용되는 typeorm-extension을 찾게되었다.
TypeOrm-seed의 기능을 동일하게 제공하면서, 최근 업데이트된 typeorm을 지원한다.

이 라이브러리로 dummy-data를 만들어 seeding하는 작업에 대해 소개하고자한다.


runner

  • seed작업은 별개의 함수로 실행된다. 따라서, package.json에 별개의 수행 스크립트를 넣어주고 실행한다.
{
	"scripts" : {
		"seed": "ts-node ./src/seed-database/seed-data-source.ts"
		}
}
  • typeorm을 사용할때와같이, DataSourceOptions를 받는데, 이와 더불어 SeederOptionsSeederfactories를 식별한다.
  • dotenv로 path를 하드코딩했는데, 이 부분은 본인에 맞게 사용하면된다.
  • 다만, 프로덕션 환경에서 이 파일을 수행하지 않을 것이란 보장이 없으므로, 그냥 하드코딩해서 .env.dev.local로 선언해두고 사용하는 것을 추천한다.
const runner = async () => {
  dotenv.config({
    path: '.env.dev.local',
  })

  const options: DataSourceOptions & SeederOptions = {
	//DataSourceOptions
    type: 'mysql',
    database: process.env.DATABASE,
    username: process.env.DBUSER,
    host: process.env.HOST,
    password: process.env.PASSWORD,
    dropSchema: true,
    synchronize: true,
    entities: [User, User_Events, Hashtag, Event, Review],

    //seed options
	seeds: [
      UserSeeder,
      HashtagSeeder,
      EventSeeder,
      ReviewSeeder,
      UserEventSeeder,
    ],
    factories: ['src/seed-database/factories/*.ts'],
  }
  const datasource = new DataSource(options)
  await datasource.initialize()

  runSeeders(datasource)
}()


factory 선언

  • entity를 선언해 값을 채운다.
  • chance라이브러리처럼 faker라이브러리로 랜덤한 데이터를 생성할 수 있다.
import { setSeederFactory } from 'typeorm-extension'
import { Hashtag } from '../../entity/Hashtag.entity'

export default setSeederFactory(Hashtag, (faker) => {
  const fakeHashtag = new Hashtag()

  fakeHashtag.hashtagName = faker.word.adjective({
    length: {
      min: 5,
      max: 50,
    },
  })
  return fakeHashtag
})



seeder 선언

  • run메서드를 오버라이딩한다.
  • unique, fk 제약 조건으로 인해 exception이 생길 것에 대비해 try-catch문으로 최소한의 데이터를 만들 수 있도록한다.
import { DataSource } from 'typeorm'
import { Seeder, SeederFactoryManager } from 'typeorm-extension'
import { Event } from '../../entity/Event.entity'

export default class EventSeeder implements Seeder {
  public async run(
    dataSource: DataSource,
    factoryManager: SeederFactoryManager
  ): Promise<any> {
    const Factory = factoryManager.get(Event)
    for (let i = 0; i < 20; i++) {
      try {
        await Factory.save()
      } catch (e) {
        i--
        continue
      }
    }
  }
}



테스트의 중요성

42서울에서 과제를 하면서 수없이도 느꼈던 것이다.
어디서 누가 제시하는지 모르는 수많은 코너케이스들에 대응하기위해서는 코드 한 줄, 한 줄에 그 코너케이스들을 모두 고려해야한다.
대응한답시고 많은 고민 끝에 과제를 끝내면, 평가받으면서 또 버그를 발견한다.


소프트웨어 테스트

버그가 없는 소프트웨어는 존재하지 않지만, 개발자는 항상 버그를 염두에 두고 코드를 작성해야한다.
개발자들이 작성하는 소프트웨어는 출시 전에 QA라는 테스트 작업을 수행하는데, 품질보증이라는 의미이다.

여기서 말하는 품질보증은 버그가 없다는 것이 아닌, 사용자에게 전달될 정도의 수준임을 보증한다.
테스트로 모든 버그를 발견할 수 없지만, 과정 중에 발견한 모든 버그를 수정할 필요는 없다.
가능한 일정내에 사용하는 사람에게 최대한의 가치를 전달할 수준으로 만드는 것이 최대의 이익을 가져다준다.



일단 Jest부터

자바스크립트 테스트 프레임워크에는 test runner, assertion, matcher 등이 있는데,
NestJs에서는 Jest와 SuperTest를 제공한다.

Jest는 Nest, Babel, Ts, Node.js, React, Angular 등 다양한 프레임워크, 라이브러리를 지원한다.


Jest에서 사용하는 용어

  • 테스트 스위트(test suite) : 테스트들을 의미있는 단위로 묶은 것
    • 테스트 스위트를 모아 더 큰 단위의 테스트 스위트를 만들 수 있다.
    • 테스트 수행에 필요한 기반을 마련한다.


describe, it

  • 테스트코드는 describe()it()로 구성된다.
  • 테스트코드의 파일명은 .spec.ts로 끝나야한다.

  • describe() : 테스트 스위트를 작성하는 블록
  • it() : 특정 테스트 시나리오를 작성한다.
    • 각 구문은 별개의 테스트로 이루어져야한다.
import { Test, TestingModule } from '@nestjs/testing'
import { UserEventsService } from './user-events.service'

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



그외 구문

  • SetUp : describe 블록 내에서 동일하게 선행되어야하는 조건이 있다면, 여기서 정의한다.
  • Teardown : describe 블록 내에서 동일하게 후처리가 필요하다면 여기서 정의한다.

그 밖에도 아래와 같은 구문이 있다.

  • beforeAll : describe내의 모든 테스트케이스 전에 1번만 수행된다.
  • beforeEach : describe내의 각 테스트케이스 수행 전마다 수행된다.
  • afterAll : describe내의 모든 테스트케이스 종료 후 수행된다.
  • afterEach : describe내의 각 테스트 종료 후 수행된다.

댓글남기기