Mocha와 Should.js를 이용해 테스트 코드를 작성하던 중, 콜백 함수 내에선 assert 기능이 정상적으로 작동하지 않는 것을 발견했다. 처음엔 Should.js 자체의 문제인 줄 알고 assert 라이브러리로 바꿔서 시도했지만 똑같은 현상이 발생했다. 아래와 같이 명백히 오류가 나야 하는 부분도 문제 없이 통과가 되었다.

 

const tempAndHum = require('tempAndHum')

it('현재 온습도 출력 테스트', () => {
    tempAndHum.getNowTah((data) => {
        (0).should.be.equal(1)
    })
})

 

 찾아본 결과, mocha에서 비동기 함수 내에서 테스트를 진행할 때는 done을 이용하여, mocha가 해당 테스트 함수가 모두 실행될 때까지 기다리도록 해야 한다. 그래서 다음과 같이 바꿨다. 여기까지는 공식 문서에도 설명이 잘 되어 있는 부분이다.

it('현재 온습도 출력 테스트', (done) => {
    tempAndHum.getNowTah((data) => {
        (0).should.be.equal(1)
        done()
    })
})

 

 

 이렇게 하고 테스트를 진행하니, 이제는 다음과 같이 엉뚱한 부분에서 에러가 떴다.

Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

   

  분명 나는 done()을 넣어 놨는데, 테스트 모듈에선 못 찾고 있었다. 그래서 다시 찾아본 결과, (0).should.be.equal(1)의 assert 문에서 이미 Error가 던져져서 코드 실행이 중단되고, 따라서 done()까지 코드가 가지 못하고 있는 상태였다. Promise를 이용한 비동기 함수라면 .catch로 해결하면 될 것이고, 콜백 함수를 이용한 비동기 문일 경우 다음과 같이 try ~ catch 문으로 테스트 함수를 감싸면 된다. 최종적으로 아래와 같다.

 

it('현재 온습도 출력 테스트', (done) => {
    tempAndHum.getNowTah((data) => { // 테스트를 원하는 함수
        try {
            // 원하는 테스트 코드 입력
            done()
        }
        catch (err){
            done(err)
        }
    })
})

 

 

 

 Express.js를 이용하여 개발을 하면서 ORM 라이브러리로 Sequelize를 사용하고 있는데, 내 예상과 다르게 작동하는 부분이 많아서 조금 애를 먹고 있었다. 그 중에서 특히 내가 가장 삽질했던 부분을 공유하고자 한다.

 

 

Sequelize 모델 불러오기

 우선 가장 먼저 ORM을 이용하려면 내 코드에 해당 모델을 불러와야 하는데, 대부분의 문서나 글에선 이미 모델을 불러온 걸 전제로 CRUD를 설명하는데, 이 모델을 가져오는 부분에서부터 막혔었다.

 

 

 

 Sequelize-cli를 이용하여 다음 명령어를 이용하여 기본적인 파일을 생성했다.

 

sequelize init

 

 이렇게 하면, models/index.js에서 실제 DB와 시퀄라이저가 연결된다. 그런데, index.js에는 모델을 불러오는 형식이 이미 지정되어 있고, 따라서 각 모델 파일들의 형식도 다음과 같이 지정되어 있다. 물론, 이러한 형태에서는 require(user.js) 형태로 불러와도 당연히 해당 모델을 쓸 수 없다. 그렇다고 다시 싹 다 Sequelize 모듈을 임포트 하기엔 지저분하고 비효율적이다. 분명 이렇게 쓰라고 만든 기능이 아닐 것이다...

// models/user.js

module.exports = function (sequelize, DataTypes) {
    return sequelize.define(
        // ...
    )
}

 

 

 

 

 여기서, models/index.js 파일을 다시 보면 재미있는 사실을 찾을수 있다.

 

'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};

// DBMS와 연결
let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, config);
}


// 각 모델을 불러옴.
fs
  .readdirSync(__dirname)
  .filter(file => {
    return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
  })
  .forEach(file => {
    const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
    db[model.name] = model;
  });

Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

 "각 모델을 불러옴"이라고 주석을 넣은 부분의 코드를 읽어 보면, 현재 index.js가 있는 폴더 내의 파일을 모두 읽고(자기 자신 제외), 이를 전부 연동(associate)하여 db라는 객체 안에 넣는다. 그리고 이 db를 익스포트한다. 그렇다면, 당연히 익스포트된 db 객체만 가져와도 DB의 모든 기능을 이용할 수 있을 것이다.

 

 

 

 따라서, 다른 파일에서 모델을 사용한다면...

const db = require('../models')
const tah = db["TempAndHum"] // 내가 만든 DB 이름

 

 이런 식으로 DB 이름을 이용하여 불러올 수 있다. models/index.js 안에서 DBMS 연결과 모델 연결을 모두 해 주기 때문에, 다른 데에선 그냥 DB 이름만 갖고 불러오면 된다. DB 이름은, 

module.exports = function (sequelize, DataTypes) {
    return sequelize.define(
        "TempAndHum", {
        id: { ...
        

 이렇게 각 모델 파일에서 정의한 이름을 따른다.

 

 

서브쿼리를 이용하여 조건을 걸고 DELETE 사용하기

 

  나는 특정 테이블을 생성 시간순으로 정렬하여, 거기서 특정 몇 개의 항만 삭제하는 함수를 구현하고자 했다. 일반적인 SQL 문에서는 서브 쿼리를 이용하여 구현할 수 있는데, 시퀄라이저에서는 서브쿼리를 다루는 방법이 좀 복잡하고 직관적이지 않았다. 그래서 그냥 직접 SQL 쿼리를 박아넣었다.

 

const db = require('../models')
const tah = db["TempAndHum"]

tah.destroy({
        where: {
            id: [Sequelize.literal(`SELECT * FROM (SELECT id FROM tempandhum ORDER BY createdAt DESC LIMIT 3) AS tmp`)]
        }
    })

 

 Sequelize.literal 함수를 통해 썡 SQL 쿼리를 넣었고, 내가 원하는 조건의 id들을 추출한 뒤 where 조건문에 넣어 조건에 따른 삭제를 수행한다. 쿼리를 SELECT * FROM ~~ AS tmp와 같이 래핑했는데, mariaDB에서는 서브쿼리에 limit가 적용이 안되서 위와 같이 편법으로 우회했다. 위 쿼리가 실제도 수행될 땐 다음과 같이 수행된다.

 

DELETE FROM `TempAndHum` WHERE `id` IN 
(SELECT * FROM (SELECT id FROM tempandhum ORDER BY createdAt DESC LIMIT 3) AS tmp)

 

'프로그래밍 > Node.js' 카테고리의 다른 글

Mocha에서 콜백 함수 테스트하기  (0) 2021.01.28

+ Recent posts