테스트에 편리하도록 논리와 빠른 경로를 분리하다

최초 발표coreycleary.me.이것은 나의 콘텐츠 블로그의 교차 게시물이다.나는 1, 2주에 한 번씩 새로운 내용을 발표한다. 만약 당신이 직접 나의 글을 당신의 우편함에 보내고 싶다면, 당신은 sign up to my newsletter할 수 있다.나는 또한 정기적으로 메모지, 기타 우수한 강좌의 링크 (다른 사람이 제공) 와 기타 무료 경품을 발송한다.
Express 애플리케이션을 테스트할 수 있는 방식으로 구성하는 방법에 대해 곤혹스러워하셨습니까?
노드의 대다수 물건과 같다.jsworld에는 Express 프로그램을 작성하고 구축하는 방법이 많이 있습니다.
그러나 가장 좋은 출발점은 일반적으로 전형적인'Hello World'예이고 다음은Express documentation 중의 예이다.
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(port, () => console.log(`Example app listening on port ${port}!`))
회선app.get('/', (req, res) => res.send('Hello World!'))은 응답을 제공하는 실제 노선이다.
따라서 새로운 HTTP 루트를 추가하려면 .get() 또는 .post 방법의 리셋에 루트 처리 코드를 추가하는 것과 같은 패턴을 따르는 것이 의미가 있는 것 같습니다.
웹 포럼 응용 프로그램이 있고 사용자를 만들고자 하는 경우 코드는 다음과 같습니다.
app.post('/api/user', async (req, res) => {
  const userName = req.body.user_name
  const userType = req.body.user_type
  try {
    await insert(userType, userName)
    res.sendStatus(201)
  } catch(e) {
    res.sendStatus(500)
    console.log(e)
  }
})
...Hello World 구조의 예를 따릅니다.
그런데 실제 테스트가 됐을 때는요?우리는 어떻게 루트를 끝까지 테스트하고 루트 처리 프로그램에 포함된 실제 사용자의 논리를 단원 테스트합니까?
현재 상황에서 테스트는 다음과 같이 보일 수 있다.
describe('POST /api/user', () => {
  before(async () => {
    await createTable('admin')
    await createTable('member')
  })

  after(async () => {
    await dropTable('admin')
    await dropTable('member')
  })

  it('should respond with 201 if user account created successfully', async () => {
    const response = await request(app)
      .post('/api/user')
      .send({user_name: "ccleary00", user_type: "admin"})
      .set('Accept', 'application/json')

      expect(response.statusCode).to.equal(201)
  })
})
현재 사용자 생성 논리가 리셋 중이기 때문에 '내보내기' 리셋만 할 수 없습니다.이러한 논리를 테스트하기 위해서, 우리는 항상 서버에 요청을 보내서 그것을 테스트해야 한다. 그래야만 POST/api/user 루트에 도달할 수 있다.
이것이 바로 우리가 위에서 한 것입니다. supertest 요청을 보내고 서버에서 온 응답에 대한 단언을 실행합니다.

공기 중의 냄새


그런데 이 일이 좀 이상해서...
하나의 단원으로 더 많은 테스트를 해야 할 것들을 위해 이런 끝에서 끝까지 테스트를 짜는 것은 이상하다.
만약에 사용자 창설 논리가 더욱 복잡해지기 시작한다면 예를 들어 전자메일 서비스를 호출하여 사용자 등록 전자메일을 보내야 하고 사용자 계정이 이미 존재하는지 확인해야 한다면 어떻게 해야 합니까?우리는 코드의 모든 다른 논리적 지점을 테스트해야 하는데,supertest를 사용하여 전과정과 끝까지의 테스트를 진행하면 곧 매우 짜증나게 변할 것이다.
다행히도 테스트 가능한 복구는 매우 간단했다.HTTP 코드와 업무 논리 코드를 분리함으로써 관심사를 더욱 잘 분리할 수 있습니다.

노선에서 논리를 끌어내다


이 라우트를 테스트할 수 있는 가장 간단한 방법은 자체 함수에 콜백의 현재 코드를 배치하는 것입니다.
export default async function createUser (req, res) => {
  const userName = req.body.user_name
  const userType = req.body.user_type
  try {
    await insert(userType, userName)
    res.sendStatus(201)
  } catch(e) {
    res.sendStatus(500)
    console.log(e)
  }
}
그런 다음 패스트트랙으로 가져옵니다.
const createUser = require('./controllers/user')
app.post('/api/user', createUser)
현재, 우리는 여전히 루트 컴파일러를 위해 끝에서 끝까지 테스트를 할 수 있으며, 이전과 같은 테스트 코드를 사용할 수 있지만, 우리는 더욱 많은 단원으로 createUser() 함수를 테스트할 수 있다.

벽돌 한 기와


예를 들어 LOUD, all caps 사용자 이름을 사용하지 못하도록 논리를 검증/변환하면 데이터베이스에 저장된 이름이 소문자인지 단언할 수 있습니다.
export default async function createUser (req, res) => {
  const userName = req.body.user_name.toLowerCase() // QUIETER!!
  const userType = req.body.user_type
  try {
    await insert(userType, userName)
    res.sendStatus(201)
  } catch(e) {
    res.sendStatus(500)
    console.log(e)
  }
}
이러한 검증/전환 논리는 사용자를 만들기 전에 사용자 이름에서 공백을 삭제하거나 공격적인 이름을 검사해야 하는 등 더욱 복잡해질 수 있습니다. 알겠습니다.
이 점에서 우리는 이 논리를 자신의 기능에 끌어들여 하나의 단원으로 테스트할 수 있다.
export function format(userName) {
  return userName.trim().toLowerCase()
}

describe('#format', () => {
  it('should trim white space from ends of user name', () => {
    const formatted = format('  ccleary00 ')
    expect(formatted).to.equal('ccleary00')
  })

  it('should convert the user name to all lower case', () => {
    const formatted = format('CCLEARY00')
    expect(formatted).to.equal('ccleary00')
  })
})
따라서 우리는 루트에 대한 리셋에 모든 논리를 포함할 필요가 없고, 이를 단독 단원으로 분해하여 더욱 쉽게 테스트를 할 수 있으며, 많은 것을 모의할 필요가 없다.
기술적으로 우리는 최초의 방식으로 이러한 테스트를 작성하여 빠른 노선에 요청을 보낼 수 있지만, 이를 하기는 훨씬 어렵다.쓰기 테스트가 어려울 때, 그들은 종종 전혀 쓰기를 당하지 않는다.

마무리


Express 응용 프로그램을 구성할 수 있는 방법은 여러 가지가 있습니다. 핵심 사용자의 논리를 '서비스' 로 추출하는 동시에 루트 컨트롤러 처리 검증을 통해 이를 더욱 분해할 수 있습니다.
그러나 현재로서는 루트 리셋에 논리를 넣지 않는 것이 관건이다.당신은 미래에 더욱 쉽게 테스트와 재구성을 진행할 수 있을 것입니다.
테스트는 어렵지 않고 쉬울 것이다.프로그램 작성 테스트가 매우 고통스럽다는 것을 발견하면, 이것은 보통 일부 코드를 재구성하거나 다시 작성해야 하는 첫 번째 힌트입니다.때때로, 대량의 코드를 작성하기 전에, 당신은 심지어 이 점을 의식하지 못하고, 재구성하는 것이 더욱 고통스러울 수도 있다.
나는 이런 상황을 피하는 가장 좋은 방법은 테스트 드라이브 개발 (TDD) 을 사용하는 것이라고 발견했다. 그것은 결국 나로 하여금 나쁜 코드를 많이 쓰는 것을 피하게 했다. (예를 들어 내가 본문에서 예시로 사용한 Express user route 코드).
먼저 테스트를 작성한 다음에 코드를 작성하는 것은 이상하게 느껴질 수 있지만, 만약 당신이 지도를 원한다면, 당신의 사고방식이 클릭check out another post I wrote on TDD here에 도움이 될 것이다.
또한 JavaScript(일반 JavaScript) 테스트를 단순화하는 데 도움을 주기 위해 많은 새로운 내용을 작성하고 있습니다.더 간단하다. 왜냐하면 나는 그것이 때처럼 복잡해야 한다고 생각하지 않기 때문이다.만약 이 새로운 게시물 중 한 편을 놓치고 싶지 않다면, 여기는 링크to subscribe to my newsletter!입니다.

좋은 웹페이지 즐겨찾기