作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Mikhail Angelov
Verified Expert in Engineering
21 Years of Experience

米哈伊尔拥有物理学硕士学位. He’s run the gamut with Node.js, Go, JavaScript spa, React.js, Flux/Redux, RIOT.js, and AngularJS.

Expertise

Share

测试是构建健壮Node的重要组成部分.js application. 适当的测试可以很容易地克服开发人员可能指出的许多缺点 Node.js development solutions.

虽然许多开发人员关注单元测试的100%覆盖率, 重要的是,您编写的代码不是单独测试的. 集成和端到端测试通过一起测试应用程序的各个部分,为您提供了额外的信心. 这些部件可能自己工作得很好, but in a large system, 代码单元很少单独工作.

Node.js和MongoDB一起构成了最近最受欢迎的二人组之一. 如果你恰好是使用它们的许多人中的一员,那么你很幸运.

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

您将看到mongo-unit包如何帮助在Node中进行集成和端到端测试.js. 以获取更全面的Node概述.js integration tests, see this article.

Dealing with a Real Database

Typically, 用于集成或端到端测试, 您的脚本将需要连接到一个真正的专用数据库以进行测试. 这包括编写在每个测试用例/套件的开始和结束处运行的代码,以确保数据库处于干净的可预测状态.

这可能对某些项目很有效,但也有一些局限性:

  • 测试环境可能相当复杂. 您需要在某个地方保持数据库运行. 这通常需要额外的工作来设置CI服务器.
  • 数据库和操作可能相对较慢. 因为数据库将使用网络连接,操作将需要文件系统活动, 快速运行数千个测试可能并不容易.
  • 数据库保存状态,这对于测试来说不是很方便. 测试应该彼此独立,但是使用公共DB可能会使一个测试影响其他测试.

On the other hand, 使用真实的数据库使测试环境尽可能接近生产环境. 这可以看作是这种方法的一个特殊优势.

使用真实的内存数据库

使用真实的数据库进行测试似乎有一些挑战. 但是,使用真实数据库的优势太好了,无法传递. 我们如何应对挑战并保持优势?

重用来自另一个平台的好的解决方案并将其应用于Node.Js的世界可以是这里的路.

Java项目广泛使用DBUnit和内存数据库.g., H2) for this purpose.

DBUnit与JUnit (Java测试运行器)集成,并允许您定义每个测试/测试套件的数据库状态, etc. 它消除了上面讨论的约束:

  • DBUnit和H2是Java库,因此不需要设置额外的环境. It all runs in the JVM.
  • 内存中的数据库使得这种状态管理非常快.
  • DBUnit使数据库配置非常简单,并允许您为每种情况保持清晰的数据库状态.
  • H2是一个SQL数据库,它与MySQL部分兼容, in major cases, 应用程序可以像使用生产数据库一样使用它.

从这些概念出发,我决定为Node做一些类似的东西.js and MongoDB: Mongo-unit.

Mongo-unit is a Node.可以使用NPM或Yarn安装. It runs MongoDB in-memory. 通过与Mocha很好地集成并提供一个简单的API来管理数据库状态,它使集成测试变得容易.

The library uses the mongodb-prebuilt NPM包,其中包含为流行操作系统预构建的MongoDB二进制文件. 这些MongoDB实例可以在内存模式下运行.

Installing Mongo-unit

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

npm install -D mongo-unit

or

yarn add mongo-unit

And, that is it. 您甚至不需要在计算机上安装MongoDB就可以使用这个包.

使用蒙古包单元进行集成测试

假设您有一个简单的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.静态的(“$ {__dirname} /静态”))
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的代码. 您可以查看完整的示例 on GitHub.

Integrating with Mocha

让Mocha运行针对mongo-unit的集成测试, 我们需要在将应用程序代码加载到Node之前运行mongo-unit数据库实例.js context. To do this, we can use the 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()
 }))

Writing Integration Tests

第一步是将测试添加到测试数据库(testData.json):

{
   "tasks": [
   {
     "name": "test",
     “开始”:“2017 - 08 - 28 - t16: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)
     })
 })
})

And, voila!

请注意,这里处理设置和删除的代码只有几行.

正如您所看到的,使用mongo-unit库编写集成测试非常容易. 我们不模拟MongoDB本身,我们可以使用相同的Mongoose模型. 我们完全控制了数据库数据,并且在测试性能上没有损失太多 fake MongoDB is running in memory.

这也允许我们将最佳单元测试实践应用于集成测试:

  • 使每个测试独立于其他测试. 我们在每次测试之前加载新数据,为每次测试提供完全独立的状态.
  • 对每个测试使用最小要求状态. 我们不需要填充整个数据库. 我们只需要为每个特定的测试设置所需的最小数据.
  • 我们可以为数据库重用一个连接. It increases test performance.

作为奖励,我们甚至可以在mongo-unit上运行应用程序本身. 它允许我们针对模拟数据库对应用程序进行端到端测试.

端到端测试

对于端到端测试,我们将使用 Selenium WebDriver and Hermione E2E test runner.

首先,我们将引导驱动程序和测试运行程序:

const mongoUnit = require('mongo-unit')
Const selenium = require('selenium-standalone')
const Hermione = require(' Hermione ')
新赫敏()./e2e/hermione.conf.js') //hermione config

seleniumInstall() //确保安装了selenium
 .然后(seleniumStart) //启动selenium web驱动程序
 .then(mongoUnit.start) // start mongo unit
 .then(testMongoUrl => {
   process.env.MONGO_URL = testMongoUrl //存储mongo的url
 })
 .then(() => {
   require('./index.js') //start application
 })
 .然后(delay(1000)) //等待一秒钟,直到应用程序启动
 .then(() => hermione.run(", heroneopts)) //运行heronee2e测试
 .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))
}

用一些数据填充数据库,并在测试完成后清理数据库, we can run our first tests:

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))

 它('应该显示任务列表',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)
   })
 })
})

正如您所看到的,端到端测试看起来与集成测试非常相似.

Wrap Up

集成和端到端测试对于任何大型应用程序都很重要. Node.特别是Js应用程序,可以从自动化测试中获益良多. With mongo-unit, 您可以编写集成和端到端测试,而不必担心这些测试带来的所有挑战.

You can find complete examples 如何在GitHub上使用mongo-unit.

Understanding the basics

  • What is an integration test?

    集成测试是一种自动化测试,用于验证系统的多个组件在各种情况下是否正确工作.

  • What does E2E stand for?

    E2E是端到端(end- end)的缩写,通常用于端到端测试的上下文中.

聘请Toptal这方面的专家.
Hire Now
Mikhail Angelov

Mikhail Angelov

Verified Expert in Engineering
21 Years of Experience

下诺夫哥罗德,下诺夫哥罗德州,俄罗斯

Member since July 6, 2015

About the author

米哈伊尔拥有物理学硕士学位. He’s run the gamut with Node.js, Go, JavaScript spa, React.js, Flux/Redux, RIOT.js, and AngularJS.

作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.