Node.js와 MongoDB로 통합 및 종단 간 테스트가 쉬워졌습니다.

게시 됨: 2022-03-11

테스트는 강력한 Node.js 애플리케이션을 구축하는 데 필수적인 부분입니다. 적절한 테스트는 개발자가 Node.js 개발 솔루션에 대해 지적할 수 있는 많은 단점을 쉽게 극복할 수 있습니다.

많은 개발자가 단위 테스트를 통해 100% 적용 범위에 중점을 두지만 작성하는 코드를 단독으로 테스트하지 않는 것이 중요합니다. 통합 및 종단 간 테스트는 응용 프로그램의 일부를 함께 테스트하여 추가적인 확신을 제공합니다. 이러한 부분은 자체적으로 잘 작동할 수 있지만 대규모 시스템에서는 코드 단위가 별도로 작동하는 경우가 거의 없습니다.

Node.js와 MongoDB는 함께 최근 가장 인기 있는 듀오 중 하나입니다. 당신이 그들을 사용하는 많은 사람들 중 하나라면 운이 좋은 것입니다.

이 기사에서는 정교한 환경이나 복잡한 설정/해제 코드를 설정할 필요 없이 데이터베이스의 실제 인스턴스에서 실행되는 Node.js 및 MongoDB 애플리케이션에 대한 통합 및 종단 간 테스트를 쉽게 작성하는 방법을 배웁니다. .

mongo-unit 패키지가 Node.js의 통합 및 종단 간 테스트에 어떻게 도움이 되는지 알 수 있습니다. Node.js 통합 테스트에 대한 보다 포괄적인 개요는 이 문서를 참조하세요.

실제 데이터베이스 다루기

일반적으로 통합 또는 종단 간 테스트의 경우 스크립트는 테스트 목적을 위해 실제 전용 데이터베이스에 연결해야 합니다. 여기에는 데이터베이스가 완전히 예측 가능한 상태인지 확인하기 위해 모든 테스트 사례/세트의 시작과 끝에서 실행되는 코드 작성이 포함됩니다.

이것은 일부 프로젝트에서 잘 작동할 수 있지만 몇 가지 제한 사항이 있습니다.

  • 테스트 환경은 상당히 복잡할 수 있습니다. 어딘가에서 데이터베이스를 계속 실행해야 합니다. 이는 종종 CI 서버로 설정하기 위해 추가 노력이 필요합니다.
  • 데이터베이스 및 작업이 상대적으로 느릴 수 있습니다. 데이터베이스는 네트워크 연결을 사용하고 작업에는 파일 시스템 활동이 필요하기 때문에 수천 개의 테스트를 빠르게 실행하는 것이 쉽지 않을 수 있습니다.
  • 데이터베이스는 상태를 유지하며 테스트에 그다지 편리하지 않습니다. 테스트는 서로 독립적이어야 하지만 공통 DB를 사용하면 하나의 테스트가 다른 테스트에 영향을 줄 수 있습니다.

반면 실제 데이터베이스를 사용하면 테스트 환경을 최대한 프로덕션에 가깝게 만듭니다. 이것은 이 접근 방식의 특별한 장점으로 볼 수 있습니다.

실제 메모리 내 데이터베이스 사용

테스트를 위해 실제 데이터베이스를 사용하는 것은 몇 가지 문제가 있는 것 같습니다. 그러나 실제 데이터베이스를 사용하는 이점은 너무 좋습니다. 문제를 해결하고 이점을 유지하려면 어떻게 해야 합니까?

다른 플랫폼의 좋은 솔루션을 재사용하고 Node.js 세계에 적용하는 것이 여기로 가는 길이 될 수 있습니다.

Java 프로젝트는 이러한 목적으로 인메모리 데이터베이스(예: H2)와 함께 DBUnit을 널리 사용합니다.

DBUnit은 JUnit(자바 테스트 실행기)과 통합되어 각 테스트/테스트 제품군 등에 대한 데이터베이스 상태를 정의할 수 있습니다. 위에서 설명한 제약 조건을 제거합니다.

  • DBUnit과 H2는 Java 라이브러리이므로 별도의 환경을 설정할 필요가 없습니다. 모두 JVM에서 실행됩니다.
  • 인메모리 데이터베이스는 이 상태 관리를 매우 빠르게 만듭니다.
  • DBUnit을 사용하면 데이터베이스 구성이 매우 간단해지고 각 경우에 대해 명확한 데이터베이스 상태를 유지할 수 있습니다.
  • H2는 SQL 데이터베이스이며 MySQL과 부분적으로 호환되므로 주요 경우 애플리케이션에서 프로덕션 데이터베이스와 같이 사용할 수 있습니다.

이러한 개념을 바탕으로 Node.js 및 MongoDB에 대해 비슷한 것을 만들기로 결정했습니다. Mongo-unit.

Mongo-unit은 NPM 또는 Yarn을 사용하여 설치할 수 있는 Node.js 패키지입니다. MongoDB 인메모리를 실행합니다. Mocha와 잘 통합되고 데이터베이스 상태를 관리하는 간단한 API를 제공하여 통합 테스트를 쉽게 만듭니다.

라이브러리는 널리 사용되는 운영 체제용으로 미리 빌드된 MongoDB 바이너리를 포함하는 mongodb 미리 빌드된 NPM 패키지를 사용합니다. 이러한 MongoDB 인스턴스는 메모리 내 모드에서 실행할 수 있습니다.

몽고 유닛 설치하기

프로젝트에 mongo-unit을 추가하려면 다음을 실행할 수 있습니다.

 npm install -D mongo-unit

또는

 yarn add mongo-unit

그리고, 그게 다야. 이 패키지를 사용하기 위해 컴퓨터에 MongoDB를 설치할 필요조차 없습니다.

통합 테스트에 Mongo 단위 사용

작업을 관리하는 간단한 Node.js 애플리케이션이 있다고 가정해 보겠습니다.

 // service.js const mongoose = require('mongoose') const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost:27017/example' mongoose.connect(mongoUrl) const TaskSchema = new mongoose.Schema({ name: String, started: Date, completed: Boolean, }) const Task = mongoose.model('tasks', TaskSchema) module.exports = { getTasks: () => Task.find(), addTask: data => new Task(data).save(), deleteTask: taskId => Task.findByIdAndRemove(taskId) }

MongoDB 연결 URL은 여기에 하드 코딩되어 있지 않습니다. 대부분의 웹 애플리케이션 백엔드와 마찬가지로 환경 변수에서 가져옵니다. 이렇게 하면 테스트 중에 URL을 대체할 수 있습니다.

 const express = require('express') const bodyParser = require('body-parser') const service = require('./service') const app = express() app.use(bodyParser.json()) app.use(express.static(`${__dirname}/static`)) app.get('/example', (req, res) => { service.getTasks().then(tasks => res.json(tasks)) }) app.post('/example', (req, res) => { service.addTask(req.body).then(data => res.json(data)) }) app.delete('/example/:taskId', (req, res) => { service.deleteTask(req.params.taskId).then(data => res.json(data)) }) app.listen(3000, () => console.log('started on port 3000'))

이것은 사용자 인터페이스가 있는 예제 애플리케이션의 스니펫입니다. 간결함을 위해 UI 코드는 생략했습니다. GitHub에서 전체 예제를 확인할 수 있습니다.

모카와 통합

Mocha가 mongo-unit에 대한 통합 테스트를 실행하도록 하려면 애플리케이션 코드가 Node.js 컨텍스트에 로드되기 전에 mongo-unit 데이터베이스 인스턴스를 실행해야 합니다. 이를 위해 mocha --require 매개변수와 Mocha-prepare 라이브러리를 사용할 수 있습니다. 이를 통해 require 스크립트에서 비동기 작업을 수행할 수 있습니다.

 // it-helper.js const prepare = require('mocha-prepare') const mongoUnit = require('mongo-unit') prepare(done => mongoUnit.start() .then(testMongoUrl => { process.env.MONGO_URL = testMongoUrl done() }))

통합 테스트 작성

첫 번째 단계는 테스트 데이터베이스( testData.json )에 테스트를 추가하는 것입니다.

 { "tasks": [ { "name": "test", "started": "2017-08-28T16:07:38.268Z", "completed": false } ] }

다음 단계는 테스트 자체를 추가하는 것입니다.

 const expect = require('chai').expect const mongoose = require('mongoose') const mongoUnit = require('../index') const service = require('./app/service') const testMongoUrl = process.env.MONGO_URL describe('service', () => { const testData = require('./fixtures/testData.json') beforeEach(() => mongoUnit.initDb(testMongoUrl, testData)) afterEach(() => mongoUnit.drop()) it('should find all tasks', () => { return service.getTasks() .then(tasks => { expect(tasks.length).to.equal(1) expect(tasks[0].name).to.equal('test') }) }) it('should create new task', () => { return service.addTask({ name: 'next', completed: false }) .then(task => { expect(task.name).to.equal('next') expect(task.completed).to.equal(false) }) .then(() => service.getTasks()) .then(tasks => { expect(tasks.length).to.equal(2) expect(tasks[1].name).to.equal('next') }) }) it('should remove task', () => { return service.getTasks() .then(tasks => tasks[0]._id) .then(taskId => service.deleteTask(taskId)) .then(() => service.getTasks()) .then(tasks => { expect(tasks.length).to.equal(0) }) }) })

그리고, 짜잔!

설정 및 해제를 처리하는 몇 줄의 코드가 있음을 주목하십시오.

보시다시피 mongo-unit 라이브러리를 사용하여 통합 테스트를 작성하는 것은 매우 쉽습니다. 우리는 MongoDB 자체를 조롱하지 않으며 동일한 Mongoose 모델을 사용할 수 있습니다. 우리는 데이터베이스 데이터를 완전히 제어할 수 있으며 가짜 MongoDB가 메모리에서 실행되고 있기 때문에 테스트 성능을 크게 잃지 않습니다.

이를 통해 통합 테스트에 대한 최상의 단위 테스트 사례를 적용할 수도 있습니다.

  • 각 테스트를 다른 테스트와 독립적으로 만드십시오. 우리는 각 테스트 전에 새로운 데이터를 로드하여 각 테스트에 대해 완전히 독립적인 상태를 제공합니다.
  • 각 테스트에 필요한 최소 상태를 사용합니다. 전체 데이터베이스를 채울 필요는 없습니다. 각 특정 테스트에 필요한 최소 데이터만 설정하면 됩니다.
  • 데이터베이스에 대해 하나의 연결을 재사용할 수 있습니다. 테스트 성능을 향상시킵니다.

보너스로 mongo-unit에 대해 애플리케이션 자체를 실행할 수도 있습니다. 이를 통해 모의 데이터베이스에 대해 애플리케이션에 대한 종단 간 테스트를 수행할 수 있습니다.

Selenium을 사용한 종단 간 테스트

종단 간 테스트를 위해 Selenium WebDriver 및 Hermione E2E 테스트 러너를 사용할 것입니다.

먼저 드라이버와 테스트 러너를 부트스트랩합니다.

 const mongoUnit = require('mongo-unit') const selenium = require('selenium-standalone') const Hermione = require('hermione') const hermione = new Hermione('./e2e/hermione.conf.js') //hermione config seleniumInstall() //make sure selenium is installed .then(seleniumStart) //start selenium web driver .then(mongoUnit.start) // start mongo unit .then(testMongoUrl => { process.env.MONGO_URL = testMongoUrl //store mongo url }) .then(() => { require('./index.js') //start application }) .then(delay(1000)) // wait a second till application is started .then(() => hermione.run('', hermioneOpts)) // run hermiona e2e tests .then(() => process.exit(0)) .catch(() => process.exit(1))

또한 몇 가지 도우미 기능이 필요합니다(간결함을 위해 오류 처리가 제거됨).

 function seleniumInstall() { return new Promise(resolve => selenium.install({}, resolve)) } function seleniumStart() { return new Promise(resolve => selenium.start(resolve)) } function delay(timeout) { return new Promise(resolve => setTimeout(resolve, timeout)) }

데이터베이스를 일부 데이터로 채우고 테스트가 완료되면 정리한 후 첫 번째 테스트를 실행할 수 있습니다.

 const expect = require('chai').expect const co = require('co') const mongoUnit = require('../index') const testMongoUrl = process.env.MONGO_URL const DATA = require('./fixtures/testData.json') const ui = { task: '.task', remove: '.task .remove', name: '#name', date: '#date', addTask: '#addTask' } describe('Tasks', () => { beforeEach(function () { return mongoUnit.initDb(testMongoUrl, DATA) .then(() => this.browser.url('http://localhost:3000')) }) afterEach(() => mongoUnit.dropDb(testMongoUrl)) it('should display list of tasks', function () { const browser = this.browser return co(function* () { const tasks = yield browser.elements(ui.task) expect(tasks.length, 1) }) }) it('should create task', function () { const browser = this.browser return co(function* () { yield browser.element(ui.name).setValue('test') yield browser.element(ui.addTask).click() const tasks = yield browser.elements(ui.task) expect(tasks.length, 2) }) }) it('should remove task', function () { const browser = this.browser return co(function* () { yield browser.element(ui.remove).click() const tasks = yield browser.elements(ui.task) expect(tasks.length, 0) }) }) })

보시다시피 종단 간 테스트는 통합 테스트와 매우 유사합니다.

마무리

통합 및 종단 간 테스트는 모든 대규모 응용 프로그램에 중요합니다. 특히 Node.js 애플리케이션은 자동화된 테스트를 통해 엄청난 이점을 얻을 수 있습니다. mongo-unit을 사용하면 이러한 테스트에 수반되는 모든 문제에 대해 걱정하지 않고 통합 및 종단 간 테스트를 작성할 수 있습니다.

GitHub에서 mongo-unit을 사용하는 방법에 대한 완전한 예를 찾을 수 있습니다.

Toptal 엔지니어링 블로그에 대한 추가 정보:

  • Node.js/TypeScript REST API 빌드, 3부: MongoDB, 인증 및 자동 테스트