个性化阅读
专注于IT技术分析

使用Node.js和MongoDB轻松进行集成和端到端测试

本文概述

测试是构建健壮的Node.js应用程序的重要组成部分。适当的测试可以轻松克服开发人员可能指出的有关Node.js开发解决方案的许多缺点。

尽管许多开发人员专注于单元测试的100%覆盖率, 但重要的是, 你编写的代码不应该单独进行测试。集成和端到端测试通过一起测试应用程序的各个部分, 使你更加放心。这些部分可能单独工作正常, 但是在大型系统中, 代码单元很少单独工作。

Node.js和MongoDB共同构成了最近最受欢迎的二人组之一。如果你碰巧是使用它们的众多用户之一, 那么你很幸运。

在本文中, 你将学习如何为在数据库的真实实例上运行的Node.js和MongoDB应用程序轻松编写集成和端到端测试, 而无需设置复杂的环境或复杂的设置/拆卸代码。

你将看到mongo-unit软件包如何在Node.js中帮助集成和端到端测试。有关Node.js集成测试的更全面概述, 请参阅本文。

处理真实数据库

通常, 对于集成或端到端测试, 你的脚本将需要连接到真正的专用数据库以进行测试。这涉及编写在每个测试用例/套件的开头和结尾处运行的代码, 以确保数据库处于干净的可预测状态。

这对于某些项目可能效果很好, 但存在一些局限性:

  • 测试环境可能非常复杂。你将需要保持数据库在某处运行。这通常需要额外的精力来设置CI服务器。
  • 数据库和操作可能相对较慢。由于数据库将使用网络连接并且操作将需要文件系统活动, 因此快速运行成千上万的测试可能并不容易。
  • 数据库保持状态, 因此测试不太方便。测试应该彼此独立, 但是使用公共数据库可能会使一个测试影响其他测试。

另一方面, 使用真实数据库会使测试环境尽可能接近生产环境。这可以看作是这种方法的特殊优势。

使用真实的内存数据库

使用真实的数据库进行测试似乎确实存在一些挑战。但是, 使用真实数据库的优势实在难以传递。我们如何应对挑战并保持优势?

重用来自另一个平台的好的解决方案并将其应用于Node.js世界可能是解决此问题的方法。

为此, Java项目广泛使用DBUnit和内存数据库(例如H2)。

DBUnit与JUnit(Java测试运行器)集成在一起, 可让你定义每个测试/测试套件等的数据库状态。它消除了上面讨论的约束:

  • DBUnit和H2是Java库, 因此你不需要设置额外的环境。所有这些都在JVM中运行。
  • 内存数据库使此状态管理变得非常快。
  • DBUnit使数据库配置非常简单, 并允许你针对每种情况保持清晰的数据库状态。
  • H2是一个SQL数据库, 与MySQL部分兼容, 因此在大多数情况下, 应用程序可以像生产数据库一样使用它。

从这些概念中, 我决定为Node.js和MongoDB做类似的事情:Mongo-unit。

Mongo-unit是一个Node.js软件包, 可以使用NPM或Yarn安装。它在内存中运行MongoDB。通过与Mocha很好地集成并提供简单的API来管理数据库状态, 它使集成测试变得容易。

该库使用mongodb预先构建的NPM软件包, 其中包含针对流行操作系统的预先构建的MongoDB二进制文件。这些MongoDB实例可以在内存模式下运行。

安装Mongo单元

要将mongo-unit添加到你的项目中, 可以运行:

npm install -D mongo-unit

or

yarn add mongo-unit

而且, 就是这样。你甚至不需要在计算机上安装MongoDB即可使用此软件包。

使用Mongo-unit进行集成测试

假设你有一个简单的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此处未进行硬编码。与大多数Web应用程序后端一样, 我们从环境变量中获取它。这将使我们在测试期间将其替换为任何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集成

为了使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的完整示例。

赞(4)
未经允许不得转载:srcmini » 使用Node.js和MongoDB轻松进行集成和端到端测试

评论 抢沙发

评论前必须登录!