Day1 - Setup & Jest Unit Test

RINM·2024년 2월 14일

TDD (Node.js)

목록 보기
1/3

요새 Nest.js를 사용한 백엔드 개발에 익숙해지다 보니 Express.js 사용법을 다 까먹은 느낌이다. Node 계열 백엔드 개발에 익숙하다고도 자신할 수 없어서 환기시켜줄 만한 것을 찾던 중 Node 환경에서의 TDD 강의를 발견했다. 인프런 깜짝 세일 최고 Node 풀스택 개발은 John Ahn 님 강의가 가장 이해도 잘가고 활용하기도 좋다. 강의 보면서 따라하고, 블로그에 정리하면 나중에 무슨 프로젝트를 하든 써먹을 수 있게 된다.

따라하며 배우는 TDD 개발

TDD 강의를 고른 이유는 요새 정보처리기사 시험 준비 중인데, 프로젝트 매니징 기법도 꽤 분량이 되다보니 관심이 갔기 때문이다.

Setup

3가지 테스트 모듈을 사용한다. Jest와 Node-mocks-http는 단위 테스트를 위하여, supertest는 통합 테스트를 위하여 사용한다. 테스트를 위해서 사용할 모듈이므로 개발 환경에서만 쓸 수 있게 저장한다.

npm i jest supertest node-mocks-http --save-dev

entry point를 작성해준다. 이 강의에서는 server.js를 사용한다. express 사용법이 슬슬 기억나기 시작한다.

const express = require('express')

// Constanst
const PORT = 5000;

// App
const app = express()

app.get('/',(req,res)=>{
    res.send("Hello World")
})

app.listen(PORT)
console.log(`APPLICATION NOW RUNNING ON PORT:${PORT}`);

express 4.16 이상 버전부터는 bodyParser 모듈 대신에 express의 내장 함수로 요청의 body를 파싱할 수 있다.

app.use(express.json())

API 처리를 router와 controller로 분리해준다.
전체적인 routing을 담당할 router.js를 작성한다.

const express = require('express')
const router = express.Router()

router.get('/',(req,res)=>{
    res.send("Hello World")
})

module.exports = router

server.js의 app에 해당 라우터를 등록시켜준다.

const productRoutes = require('./routes')
app.use("/api/products",productRoutes)

이제 해당 경로로 접속하면 routes.js에 정의한대로 Hello World가 반환된다.

이제 controller도 따로 나누어준다. products 밑에서 오는 요청을 담당할 products.js를 만든다.

exports.hello = (req,res)=>{
    res.send("Hello World")
}

이렇게 만든 컨트롤러를 routes.js에 등록시킨다. 이렇게 하니 nest의 module - controller와 비슷해졌다.

const productController = require('./controller/products')
router.get('/',productController.hello)

DB는 MongoDB를 사용한다. 졸업프로젝트할 때 만들어둔 M0 Tier Cluster가 있어 재사용한다. node에서 mongoDB 사용을 위하여 mongoose를 설치해준다.

npm i mongoose --save

DB URI, USER 이름, Password와 사용할 데이터베이스 이름 등을 넣어 연결해준다. MongoDB 홈페이지에서 네트워크 연결이 허용되어 있는지 꼭 확인하자.

const mongoose = require('mongoose')
const dbURI = `mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASSWORD}@movieweb.nvzwk.mongodb.net/${process.env.DB_NAME}?retryWrites=true&w=majority`
mongoose.connect(dbURI)

Product 모델을 생성한다. 각 field를 정의하는 스키마를 작성하고, 이것을 모델로 바꾸는 순서이다.

const mongoose = require('mongoose')

const productSchema = new mongoose.Schema({
    name:{
        type: String,
        required: true
    },
    description:{
        type: String,
        required: true
    },
    price:{
        type: Number
    }
})

const Product = mongoose.model("Product",productSchema)
module.exports = Product;

Jest

Jest는 테스트를 위한 프레임워크이다. 테스트 케이스를 만들어서 어플리케이션 코드의 동작을 확인할 수 있다. Jest를 사용하기 위하여 package.json의 test 스크립트를 수정해준다.

  "scripts": {
    "test": "jest --watchAll"
  },

jest는 {filename}.test.js 나 {filename}.spec.js 형태의 파일, 그리고 tests 폴더의 모든 파일에서 테스트 케이스를 찾는다.
describe는 테스트를 그룹화하는 블록을, it는 개별 테스트를 정의한다. 각각의 it는 expect와 matcher로 구성되어 있다. expect 함수는 값을 테스트하는데 사용되며, matcher는 이때 다른 방법으로 그 값을 테스트할 수 있도록 한다.

test('two plus two is four', () =>{
    expect(2 + 2).toBe(4);
})

test('two plus two is not five', () =>{
    expect(2 + 2).not.toBe(5);
})

예를 들어 위의 두 테스트의 경우 toBe() matcher로 값을 테스트하고 있다. npm test로 테스트를 실행하면 두 테스트 모두 pass한 것을 볼 수 있다.

jest.fn() 함수는 mock 함수를 만들어 테스트하려는 코드가 의존하는 부분을 대체해준다. 테스트하려는 코드가 어떻게 실행되고 호출되는지를 알 수 있다. 반환할 값을 직접 지정할 수 있고 expect와 matcher를 사용하여 어떤 인자로 호출되었는지, 몇 번 호출되었는지도 알아볼 수 있다.
TDD에서는 실제 기능을 만들기 전에 단위 테스트용 코드를 먼저 작성한다. Product 데이터를 저장하기 위한 함수(Create)를 만들어야한다면 products.test.js를 먼저 작성해주어야 한다.
products 컨트롤러에 만드려는 기능을 담당하는 함수 (createProduct)가 있는지 확인하는 테스트를 정의한다.

const productController = require('../../controller/products')

describe("Product Controller Create", () =>{
    test("should have a createProduct function",()=>{
        expect(typeof productController.createProduct).toBe("function")
    })
})

products 컨트롤러에 가서 같은 이름으로 실제 함수를 선언한 뒤 테스트를 돌려보면 다음과 같이 pass된 모습을 볼 수 있다.

createProduct 함수는 DB에 새 Product 객체를 추가하는 함수다. 이 함수가 Product 모델을 제대로 호출하는지 테스트할 수 있다. matcher로 toBeCalled를 사용한다.

test("should call ProductModel.create", () => {
    productController.createProduct();
    expect(productModel.create).toBeCalled();
})

이때 Product 모델의 create 함수는 테스트하려는 모듈 밖의 것이므로 mock 함수를 만들어 대체해준다. 컨트롤러의 createProduct에서 productModel의 create 함수를 호출하도록 작성한 뒤 테스트를 돌려보면 당연히 테스트가 통과한다.

const productModel = require('../models/Product')

exports.createProduct = () =>{
    productModel.create();
}

0개의 댓글