상권's

TIL 38 (과제리뷰, should, supertest)(2021.11.17) 본문

~2022 작성 글/TIL

TIL 38 (과제리뷰, should, supertest)(2021.11.17)

라마치 2021. 11. 17. 20:38
문제
세로와 가로의 길이가 각각 M, N인 방의 지도가 2차원 배열로 주어졌을 때, 1은 장애물을 의미하고 0 이동이 가능한 통로를 의미합니다. 로봇은 한 번에 임의의 k칸 직진과 90도 회전 중 1가지 동작을 할 수 있다. 로봇의 현재 위치와 방향, 목표 지점과 방향이 함께 주어집니다. 이 때, 방향은 위쪽이 1, 오른쪽이 2, 아래쪽이 3, 왼쪽이 4로 주어집니다. 로봇이 목표 지점까지 도달해 목표 방향으로 회전하는 데 필요한 동작의 수를 리턴해야 합니다.

입력
인자 1 : room
배열을 요소로 갖는 배열room.length는 Mroom[i]는 number 타입을 요소로 갖는 배열room[i].length는 Nroom[i][j]는 세로로 i, 가로로 j인 지점의 정보를 의미room[i][j]는 0 또는 1

인자 2 : src
number 타입을 요소로 갖는 배열src.length는 2src[i]는 0 이상의 정수src의 요소는 차례대로 좌표평면 위의 y좌표, x좌표

인자 3 : sDir
number 타입의 자연수

인자 4 : dst
number 타입을 요소로 갖는 배열dst.length는 2dst[i]는 0 이상의 정수dst의 요소는 차례대로 좌표평면 위의 y좌표, x좌표

인자 3 : dDir
number 타입의 자연수

출력
number 타입을 리턴해야 합니다.

주의사항
M, N은 20 이하의 자연수입니다.src, dst는 항상 로봇이 지나갈 수 있는 통로입니다.src에서 dst로 가는 경로가 항상 존재합니다.목표 지점에 도달한 후 방향까지 일치해야 합니다.직진은 1칸 직진이 아니라 임의의 k칸을 직진할 수 있습니다. 즉 한번의 직진 명령으로 장애물이 없는 한 계속 갈 수 있습니다.왼쪽에서 오른쪽 또는 아래에서 위쪽으로 방향을 바꾸는 데 총 2번의 회전 동작이 필요합니다.
  // 큐를 이용해서 문제를 해결한다.
  // 방향에 대한 숫자와 해당 방향으로 갈 수 있는 x, y를 정한다.
  // 해당 방향이 0이라면, 해당 숫자와 같은지 여부를 확인을 하고 회전 카운트를 올린다.
  // 해당 방향으로 계속 진행을 해본다
  // 가면서 회전 카운트와 진행 방향 +1을 한 숫자를 넣어준다.
  // 이렇게 반복하다가 해당 위치에 도달을 하면
  // 회전 방향과 비교해서 회전 카운트를 추가시킨다

첫 수도코드 => 회전시키는 부분까지 고려를 해야되다 보니 코드 구현이 어려웠습니다. 다른 블로그나 레프런스를 보고 더 직괸적이고 이해하기 쉬운 코드가 있다면 분석해보고 올려보도록 하겠습니다.


어제와 오늘은 쇼핑몰을 데이터베이스와 연결하는 과제를 진행했습니다.

어제 학습했던 MVC 패턴을 이용해서 기능들을 구현해보았습니다.

const express = require('express');
const router = express.Router();
const itemsRouter = require('./items');
const usersRoter = require('./users')

// TODO: Endpoint에 따라 적절한 Router로 연결해야 합니다.
router.use('/items', itemsRouter);
router.use('/users', usersRoter);

module.exports = router;
const router = require('express').Router();
const controller = require('./../controllers');

router.get('/:userId/orders', controller.orders.get);
router.post('/:userId/orders', controller.orders.post);

module.exports = router;

위와 같은 라우터를 만들고 client에서 주문(post)과 주문내역을 조회(get) 부분을 구현했습니다.

 

먼저 controller 일부분은 아래와 같이 구현해보았습니다. model에 요청하고 받은 정보를 처리하는 부분입니다.

get method는 userId를 받아서 주문내역을 리턴합니다.

post method는 userId와 주문하려는 정보(orders, totalprice)를 받아서 주문을 진행합니다.

주문이 성공적으로 이뤄지면 주문내역 페이지로 넘어가게 됩니다.

  orders: {
    get: (req, res) => {
      const userId = req.params.userId;
      // TODO: 요청에 따른 적절한 응답을 돌려주는 컨트롤러를 작성하세요.
      models.orders.get(userId, (error, result) => {
        if (error) {
          res.status(400).send(error)
        } else {
          res.status(200).json(result)
        }
      })
    },
    post: (req, res) => {
      const userId = req.params.userId;
      const { orders, totalPrice } = req.body;
      // TODO: 요청에 따른 적절한 응답을 돌려주는 컨트롤러를 작성하세요.
      models.orders.post(userId, orders, totalPrice, (error, result) => {
        if (error || orders === undefined) {
          res.status(400).send(error)
        } else {
          res.status(201).json(result)
        }
      })
    },
  },

controller를 통해서 model에 요청이 들어오면 처리하는 부분입니다.

 

먼저 get method를 통해서 주문 내역을 조회합니다.

여기서 SELECT 문을 구현을 할 때, order_items를 통해서 table 마다 연결될 수 있는데, orders table에서 갖고오는 정보가 많아 FROM 뒤에 orders table을 넣었습니다. 그러니 정보가 제대로 조회가 되질 않았고, 페어분의 도움으로 해결할 수 있었습니다.

=> 

처음에 구현했던 SELECT 문인데, LEFT JOIN 말고 JOIN으로 쓰면 처음 구상했던 코드로도 구현이 가능했습니다. JOIN 별로 학습을 하긴 했는데 더 추가적인 노력을 해야 될 거 같습니다. 

const queryString = `SELECT orders.id, orders.created_at, orders.total_price, items.name, items.price, items.image, order_items.order_quantity FROM orders
JOIN order_items ON order_items.order_id = orders.id 
JOIN items ON order_items.item_id = items.id
WHERE ${userId} = orders.user_id
`;

 

다음은 post method를 통해서 주문을 진행합니다.

orders table에 주문 정보가 INSERT 되고 그렇게 만들어진 orders.id 를 order_items에 추가를 해야합니다.

 

처음에는 구글링을 통해서 LAST_INSERT_ID 라는 기능을 보고 진행을 하려고 하였으나, LAST_INSERT_ID의 기능으로 할 수 없는 부분이라는 것을 알게 되었습니다.

 

이후에 아래의 코드처럼 진행한 이후에, 성공적으로 처리가 되면(result)  해당 insertId를 통해서 id 값을 받아오고, 이를 order_items에 반영할 수 있었습니다.

 

insertId
result.insertId 는 앞 선 sql 문이 성공적으로 결과를 리턴할 경우, auto_increment로 자동적으로 만들어지는 pk를 불러올 수 있습니다.
LAST_INSERT_ID()
With no argument, LAST_INSERT_ID() returns a BIGINT UNSIGNED (64-bit) value representing the first automatically generated value successfully inserted for an AUTO_INCREMENT column as a result of the most recently executed INSERT statement. The value of LAST_INSERT_ID() remains unchanged if no rows are successfully inserted.

 

아래의 for 문의 경우에는 인자로 받는 orders가 객체의 형태로 되어 있어서 배열로 만들어주고, 해당 부분을 ?로 넣고 진행을 해주었습니다.(한 번 주문에 여러 개의 상품이 있을 수 있기 때문에)

 

여기에서 order_id(result.insertId)는 order_items 테이블에 들어갈 때, 고정적으로 넣을 수 있는 부분이여서 item_id와 order_quantity 부분만 ? 로 해서 처리를 해주려고 하니( VALUES (order_id, ?) ) 또 에러가 나었는데, 페어분께서 아래의 코드로 진행을 했다고 하셔서 통과를 할 수 있었습니다.

 

In mysql a ? is a placeholder in a prepared statement. It will be replaced by whatever value is bound from the client before the statement is executed.

mysql에서 ?는 준비된 문의 자리 표시자입니다. 문이 실행되기 전에 클라이언트로부터 바인딩된 값으로 대체됩니다.
  orders: {
    get: (userId, callback) => {
      // TODO: 해당 유저가 작성한 모든 주문을 가져오는 함수를 작성하세요
      const queryString = `SELECT orders.id, orders.created_at, orders.total_price, items.name, items.price, items.image, order_items.order_quantity FROM order_items
      LEFT JOIN orders ON order_items.order_id = orders.id 
      LEFT JOIN items ON order_items.item_id = items.id
      WHERE ${userId} = orders.user_id
      `;
      db.query(queryString, (error, result) => {
        callback(error, result);
      })
    },
    post: (userId, orders, totalPrice, callback) => {
      // TODO: 해당 유저의 주문 요청을 데이터베이스에 생성하는 함수를 작성하세요
      const queryString = 
        `INSERT INTO orders (user_id, total_price) VALUES (${userId}, ${totalPrice});`
        ;
      let order_id = ''
      db.query(queryString, (err, result) => {
        if (err || orders === undefined) callback(err);
        else {
          order_id = result.insertId;
          let orderArr = []
          for (let order of orders) {
            orderArr.push([order_id, order.itemId, order.quantity]);
          }
          const orderQueryString = 
            `INSERT INTO order_items (order_id, item_id, order_quantity) VALUES ?`
          db.query(orderQueryString, [orderArr], (err, result) => {
            callback(err, result);
          });
        }
      })
    }
  }

이번 과제를 통해서 MVC 의 기본적인 로직을 이해를 할 수 있었습니다. 아직 SQL 문 작성이나, ? 와 []을 사용하는 방법이 익숙하지 않지만 꾸준히 학습해서 백엔드 개발자로써 역량을 키워야겠습니다.


should.js 출처

should is an expressive, readable, test framework agnostic, assertion library. Main goals of this library to be expressive and to be helpful. It keeps your test code clean, and your error messages helpful.

 

should는 표현적이고 읽기 쉬우며 테스트 프레임워크에 구애받지 않는 assertion 라이브러리입니다. 이 라이브러리의 주요 목표는 표현력과 도움이 되는 것입니다. 이것은 당신의 테스트 코드를 깨끗하게 유지하고 당신의 오류 메시지를 돕습니다.


SuperTest 출처

 

슈퍼 테스트는 EXPRESS 통합 테스트용 라이브러리

 

내부적으로 EXPRESS 서버를 구동시켜 실제 요청을 보낸 뒤 결과를 검증

 

HTTP assertions made easy via superagent.

http assertion은 superagent를 통해서 쉽게 수행할 수 있습니다.

 

About

The motivation with this module is to provide a high-level abstraction for testing HTTP, while still allowing you to drop down to the lower-level API provided by superagent.

 

SuperTest works with any test framework, here is an example without using any test framework at all:

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

const app = express();

app.get('/user', function(req, res) {
  res.status(200).json({ name: 'john' });
});

request(app)
  .get('/user')
  .expect('Content-Type', /json/)
  .expect('Content-Length', '15')
  .expect(200)
  .end(function(err, res) {
    if (err) throw err;
  });

오늘 배운 테스트 코드 메소드

instanceOf() 는 res.body의 종류를 확인한다.
lengthOf() 는 res.body의 길이를 확인한다
expect() supertest는 상태코드를 확인할 수 있는 벨리데이션을 제공한다.
property() 는 첫번째 인자는 res.body에 key가 있는 지 여부를 확인하고, 
두번째 인자는 그 key의 값이 맞는 지를 확인한다.

오늘 배운 API 작성 팁

app.get('/users', function(req, res) { // '/users?limit=1' 이런 식으로 입력이 될 수 있다.
    req.query.limit = req.query.limit || 10 // limit가 입력이 안될 경우에는, 기본적으로 10을 설정해준다.
    let limit = parseInt(req.query.limit, 10) // limit 가 쿼리로 입력되었을 경우, type은 string 이기때문에 parseInt()를 통해서 10진수로 만들어준다.
    if(Number.isNaN(limit)) return res.status(400).end() // isNaN을 통해서 NaN인지 여부를 확인하고 해당할 경우 400을 리턴한다.
    res.json(users.slice(0,limit));
});

app.get('/users/:id', function(req, res) {
    let id = parseInt(req.params.id, 10)
    if(Number.isNaN(id)) return res.status(400).end()
    let user = users.filter((el) => el.id === id)[0]
    if(!user) return res.status(404).end() // filter를 돌렸을 때, 해당하는 값이 하나도 없다면 undefined가 리턴된다.
    res.json(user)
})
Comments