Dev/SW Engineering

[TDD] 통합 테스트(Integration Test)

HJChung 2021. 2. 26. 04:57

이전에 [TDD] TDD 시작하기 (Unit Test, Jest)에서 Unit Test에 대해서 알아보고 작성해보았다. 

이제 Integration Test에 대해 알아보고자 한다. 

 

Integration Test

통합 테스트란 모듈을 통합하는 단계에서 수행하는 테스트로, Unit Test를 통해 각 모듈들이 잘 작동되는 것을 확인했다면 이제 이 각 모듈들을 연동해서 테스트를 수행해야하는데, 이를 통합 테스트라고 한다. 

 

통합 테스트를 하는 이유

  1. 모듈들의 상호 작용이 잘 이루어지는지 검증하기 위해
  2. 통합하는 과정에서 발생할 수 있는 오류를 찾기 위해

Supertest

단위 테스트시에는 Jest를 사용하였는데, 통합 테스트에는 Supertest 모듈을 사용해서 진행할 수 있다. 

Supertest는 ExpressJS 통합 테스트용 라이브러리로, 내부적으로 Express 서버를 구동시켜 실제 요청을 보낸 뒤 결과를 검증한다. 

 

기본적인 사용 방법

const request = require('supertest');
const express = require('express');

const app = express();

//클라이언트에서 /user 엔드포인트로 들어오는 요청을 처리하는 원본 소스
app.get('/user', function(req, res){
	res.status(200).json({name: 'grace'});
}

//위 원본 소스에 대한 통합 테스트 소스
request(app)
    .get('/user')
    .expect('Content-Type', /json/)
    .expect('Content-Length', '15')
    .expect(200)
    .end(function(err, res) {
    	if(err) throw err;
    });

 

통합 테스트 해보기

 

[TDD] TDD 시작하기 (Unit Test, Jest)

[TDD] node-mocks-http 모듈, Jest의 beforeEach 사용

에 거쳐서 완성된

실제 동작 코드

//route.js
const express = require('express');
const router = express.Router(); //express.Router 클래스를 이용해서 Router 모듈로 작성하기
const productController = require('./controller/products');

//Route 정의
router.post('/', productController.createProduct);
// controller/products.js
const productModel = require('../models/Product');

exports.createProduct = async (req, res, next) => {
  try {
    const createdProduct = await productModel.create(req.body);
    res.status(201).json(createdProduct);
  } catch (error) {
    next(error);
  }
};

에 대한 통합 테스트를 진행해보자.

 

유닛 테스트 단계에서는 Database의 작동이 정상적이라는 가정 하에 진행하였으므로 Mock 함수로 이를 대체하였지만 

통합 테스트 단계에서는 실제 API의 작동이 잘 되는지를 테스트해보는 것이기 때문에 실제로 API요청을 실제 동작 코드로 보내보고, Database도 잘 작동하는지 확인을 해봐야 한다. 

 

1) 통합테스트를 위한 폴더 생성

통한 테스트를 위한 integration 폴더

  • 통합 테스트 파일 [대상 이름].test.init.js

2) 통합 테스트 작성하기

//products.init.test.js

const request = require('supertest');
const app = require('../../server');
const newProduct = require('../data/new-product.json'); // 테스트용으로 넣어줄 데이터

// 통합 테스트케이스 만들기
it('POST /api/products', async () => {
  //supertest
  const response = await request(app) //해당 API결과 비동기로 결과를 반환해주기 때문에 여기서도 받아줘야하며
    .post('/api/products') //해당 API의 엔드포인트와 요청이 이것이므로
    .send(newProduct); // 해당 API에 보내줘야하는 것이 있으므로 (const createdProduct = await productModel.create(req.body);)
  //이렇게 했을 때 예상 결과가,
  expect(response.statusCode).toBe(201); // API 결과 상태코드가 201이어야 하므로 (res.status(201).json(createdProduct);
  expect(response.body.name).toBe(newProduct.name);
  expect(response.body.description).toBe(newProduct.description);
});

 

3) 에러 처리를 위한 통합 테스트 작성

먼저 에러 헨들러가 없다면 에러 헨들러(미들웨어)부터 만들어준다. 

//server.js

app.use((error, req, res, next) => {
	res.status(500).json({message: error.message}))
}

그리고 에러 처리를 위한 통합 테스트를 작성해준다. 

//products.init.test.js

//에러 처리를 위한 통합 테스트케이스
// 즉, catch (error) {
//     next(error);
//   } 이 부분이 잘 처리되고 있는지를 검사하는 테스트케이스 이다.
it('should return 500 on POST /api/products', async () => {
  request(app).post('/api/products').send({ nema: 'error발생용 데이터' }); //에러를 발생시켜야 하니까 잘못된 형태의 데이터를 보내보자
  //이렇게 했을 때 예상 결과가,
  expect(response.statusCode).toBe(500); // 서버에러가 발생하기 때문에 500
  expect(response.body).toStrictEqual({
    message: 'Product validation failed: description: Path `description` is required.',
  }); //서버에러 발생시 에러 메세지
});

 


 

결과적으로 

실제 동작 코드

//route.js
const express = require('express');
const router = express.Router(); //express.Router 클래스를 이용해서 Router 모듈로 작성하기
const productController = require('./controller/products');

//Route 정의
router.post('/', productController.createProduct);
// controller/products.js
const productModel = require('../models/Product');

exports.createProduct = async (req, res, next) => {
  try {
    const createdProduct = await productModel.create(req.body);
    res.status(201).json(createdProduct);
  } catch (error) {
    next(error);
  }
};

에 대한 통합 테스트 소스 코드는

const request = require('supertest');
const app = require('../../server');
const newProduct = require('../data/new-product.json'); // 테스트용으로 넣어줄 데이터

// 통합 테스트케이스 만들기
it('POST /api/products', async () => {
  //supertest
  const response = await request(app) //해당 API결과 비동기로 결과를 반환해주기 때문에 여기서도 받아줘야하며
    .post('/api/products') //해당 API의 엔드포인트와 요청이 이것이므로
    .send(newProduct); // 해당 API에 보내줘야하는 것이 있으므로 (const createdProduct = await productModel.create(req.body);)
  //이렇게 했을 때 예상 결과가,
  expect(response.statusCode).toBe(201); // API 결과 상태코드가 201이어야 하므로 (res.status(201).json(createdProduct);
  expect(response.body.name).toBe(newProduct.name);
  expect(response.body.description).toBe(newProduct.description);
});

//에러 처리를 위한 통합 테스트케이스
// 즉, catch (error) {
//     next(error);
//   } 이 부분이 잘 처리되고 있는지를 검사하는 테스트케이스 이다.
it('should return 500 on POST /api/products', async () => {
  request(app).post('/api/products').send({ nema: 'error발생용 데이터' }); //에러를 발생시켜야 하니까 잘못된 형태의 데이터를 보내보자
  //이렇게 했을 때 예상 결과가,
  expect(response.statusCode).toBe(500); // 서버에러가 발생하기 때문에 500
  expect(response.body).toStrictEqual({
    message: 'Product validation failed: description: Path `description` is required.',
  }); //서버에러 발생시 에러 메세지
});

 

가 된다. 

 

 

 

 

 

reference

따라하며 배우는 TDD 개발 - John Ahn

[Node.js] 슈퍼 테스트(Super Test)