TIL| TDD 방법론

Yeseul Han·2023년 9월 19일
0

Why?


PROS


  1. 명확한 목표: 테스트를 먼저 작성함으로써, 어떤 기능을 구현할 것인지에 대한 명확한 목표가 생김
  2. 높은 코드 품질: 리팩토링 단계에서 코드의 품질을 지속적으로 향상시킬 수 있음
  3. 신뢰성 있는 코드: TDD를 통해 개발된 코드는 테스트에 의해 검증되므로, 실제로 동작하는 코드에 대한 높은 신뢰성을 가짐.
  4. 빠른 피드백: 즉각적인 테스트 결과를 통해 현재의 코드 상태를 확인하고, 문제가 발생하면 즉시 수정할 수 있음.

CONS


개발 속도가 다소 느려질 수 있다.

THEN HOW?


  1. 실패하는 작은 단위 테스트를 작성한다. 처음에는 컴파일조차 되지 않을 수 있다.
  2. 빨리 테스트를 통과하기 위해 프로덕션 코드를 작성한다. 이를 위해 정답이 아닌 가짜 구현 등을 작성할 수도 있다.
  3. 그 다음의 테스트 코드를 작성한다. 실패 테스트가 없을 경우에만 성공 테스트를 작성한다.
  4. 새로운 테스트를 통과하기 위해 프로덕션 코드를 추가 또는 수정한다.
  5. 1~4단계를 반복하여 실패/성공의 모든 테스트 케이스를 작성한다.
  6. 개발된 코드들에 대해 모든 중복을 제거하며 리팩토링한다.

TEST


Unit Tests (단위 테스트):

  • 함수,메서드 등 작은 단위의 코드에 대한 테스트
  • 서비스나 헬퍼 함수등의 별도의 의존성이 없는 코드부터 시작하는 것이 좋다
  • 예: createProduct() 함수가 올바르게 상품을 생성하는지 확인하는 테스트

Integration Tests (통합 테스트):

  • 서비스와 리포지토리, 컨트롤러 등 여러 모듈이 올바르게 통합되어 동작하는지 확인하는 테스트
  • Nest.js에서는 Test.createTestingModule()을 사용하여 전체 애플리케이션을 로드하고 테스트한다.
  • 엔드포인트가 올바른 응답을 반환하는지 확인하는 테스트 등

E2E Tests (End-to-End 테스트):

  • E2E 테스트는 애플리케이션의 전체 흐름을 테스트. 실제 요청을 보내고 결과를 검증한다
  • Nest.js에서는 Test.createTestingModule()을 사용하여 전체 애플리케이션을 로드하고 테스트
    예: 실제 HTTP 요청을 보내 products 엔드포인트가 올바르게 동작하는지 확인하는 테스트.

WORKFLOW


  • Service Layer에서 시작하기:
    복잡한 로직이나 연산이 포함된 서비스의 함수부터 단위 테스트를 작성합니다.
    이를 통해 서비스의 핵심 로직이 올바르게 동작하는지 먼저 검증할 수 있습니다.
  • Controller Layer로 이동하기:
    컨트롤러의 엔드포인트와 연관된 통합 테스트를 작성합니다.
    이를 통해 엔드포인트가 예상대로 응답하는지, 서비스와 올바르게 연동되는지 확인합니다.

  • E2E Tests 작성하기:
    애플리케이션의 전체 흐름에 대한 테스트를 작성합니다.
    이 단계에서는 데이터베이스 연결 등 외부 의존성과의 연동도 함께 테스트합니다.

//Service Layer
// createProduct() => 제품 생성 시 데이터베이스에 제품이 잘 저장되는지 확인
describe('ProductsService', () => {
  let service: ProductsService;
  let mockRepository = {
    create: jest.fn(),
    save: jest.fn(),
  };

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        ProductsService,
        { provide: getRepositoryToken(Product), useValue: mockRepository },
      ],
    }).compile();

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

  it('should create a product', async () => {
    const productDto = {
      name: 'Test Product',
      price: 100,
      details: 'Details about the product',
      // ... other fields
    };
    mockRepository.create.mockReturnValue(productDto);
    mockRepository.save.mockResolvedValue(productDto);

    const result = await service.createProduct(productDto);
    expect(result).toEqual(productDto);
  });
});
//Contoller Layer
// createProduct() => HTTP 요청을 통해 제품이 잘 생성되는지 확인
describe('ProductsController', () => {
  let controller: ProductsController;
  let mockService = {
    createProduct: jest.fn(),
  };

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      controllers: [ProductsController],
      providers: [
        { provide: ProductsService, useValue: mockService },
      ],
    }).compile();

    controller = module.get<ProductsController>(ProductsController);
  });

  it('should create a product', async () => {
    const productDto = {
      name: 'Test Product',
      price: 100,
      details: 'Details about the product',
      // ... other fields
    };
    mockService.createProduct.mockResolvedValue(productDto);

    const result = await controller.createProduct(productDto);
    expect(result).toEqual(productDto);
  });
});
//Products E2E Test
// createProduct() => 전체 흐름을 테스트하여 제품이 잘 생성되는지 확인.
describe('Products (e2e)', () => {
  let app;

  beforeEach(async () => {
    const moduleFixture = await Test.createTestingModule({
      imports: [AppModule],  // Make sure AppModule imports everything necessary
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  it('/products (POST)', () => {
    return request(app.getHttpServer())
      .post('/products')
      .send({
        name: 'Test Product',
        price: 100,
        details: 'Details about the product',
        // ... other fields
      })
      .expect(201)
      .expect({
        id: expect.any(Number),
        name: 'Test Product',
        price: 100,
        details: 'Details about the product',
        // ... other fields
      });
  });
});
describe(): Jest에서 테스트를 그룹화하는 함수입니다. 첫 번째 인자로는 그룹명, 두 번째 인자로는 해당 그룹의 테스트들을 정의하는 함수가 들어갑니다.

beforeEach(): 각 테스트 케이스 실행 전에 매번 실행되는 함수입니다. 여기서는 각 테스트가 시작되기 전에 ProductService 인스턴스를 새로 생성해줍니다.

it() / test(): 실제 테스트 케이스를 정의하는 함수입니다. 첫 번째 인자는 테스트 케이스의 설명, 두 번째 인자는 테스트 로직을 담은 함수가 들어갑니다.

expect(): Jest의 assertion 함수입니다. 여기서는 생성된 상품의 id가 존재하는지, 이름과 가격이 올바른지 확인하고 있습니다.
profile
코딩 잘하고 싶다

0개의 댓글

관련 채용 정보