【译】测试驱动开辟:运用 Node.js 和 MongoDB 构建 Todo API

本文转载自:众成翻译
译者:文蔺
链接:http://www.zcfy.cc/article/746
原文:https://semaphoreci.com/community/tutorials/a-tdd-approach-to-building-a-todo-api-using-node-js-and-mongodb

进修怎样运用测试驱动开辟的体式格局,用 Node.js、MongoDB、Mocha 和 Sinon.js 开辟 Todo API。

简介

测试是软件开辟历程当中的一个完悉数份,它协助我们提拔软件质量。有很多种测试要领,如手动测试,集成测试,功用测试,负载测试,单元测试等等。在本文中,我们将会遵照测试驱动开辟的划定规矩编写代码。

单元测试是什么?

Martin Fowler 将单元测试定义以下:

  • 起首一个观点,单元测试是低层次的,专注于软件体系的一小部份;

  • 其次,单元测试通常是由顺序员运用通例东西本身编写的 —— 唯一的区别是运用某种单元测试框架;

  • 再次,单元测试估计比其他范例的测试显著地更快。

在本教程中,我们将会运用 Node.js 和 MongoDB 构建一个 Todo API。我们起首会给临盆代码写单元测试,然后才会真正写临盆代码。

环境

  • Express.js

  • MongoDB

  • Mocha

  • Chai

  • Sinon.js

项目设置

在我们真正开辟 API 之前,我们必需设置文件夹和端点(end point)。

在软件项目中,没有最好的运用架构。本教程运用的文件构造,请看该 GitHub 堆栈。

如今来竖立端点(endpoints):

《【译】测试驱动开辟:运用 Node.js 和 MongoDB 构建 Todo API》

装置依靠

Node.js 有本身的包治理东西 NPM。要进修更多关于 NPM 的学问,能够看我们的另一篇教程,《Node.js Package Manager tutorial》

好,我们来装置项目依靠。

npm install express mongoose method-override morgan body-parser cors —save-dev

定义 Schema

我们会运用 Mongoose 作为 Node.js 中的对象文档模子(Object Document Model),它事情起来和典范的 ORM一样,就像 Rails 顶用 ActiveRecord一样。Mongoose 帮我们更方便地接见 MongoDB 敕令。起首我们为 Todo API 定义 schema。

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// Defining schema for our Todo API
var TodoSchema = Schema({
  todo: {
    type: String
  },
  completed: {
    type: Boolean,
    default: false
  },
  created_by: {
    type: Date,
    default: Date.now
  }
});
//Exporting our model
var TodoModel = mongoose.model('Todo', TodoSchema);

module.exports = TodoModel;

Mongoose 中的一切都是从 schema 最先。每一个 schema 对应一个 MongoDB 鸠合,它定义了鸠合中文档的外形。

在上面的 todo schema 中,我们竖立了三个字段来存储 todo 形貌、状况和竖立日期。该 schema 协助 Node.js 运用明白怎样将 MongoDB 中的数据映射成 JavaScript 对象。

搭建 Express Server

我们将运用 Express 来搭建服务器,它是一个小型 Node.js web 框架,供应了一个壮大的功用集,用于开辟Web运用顺序。

我们继承,搭建 Express server。

起首,我们要按下面如许引入项目依靠:

var express = require('express');
var mongoose = require('mongoose');
var morgan = require('morgan');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var app = express();
var config = require('./app/config/config');

接着,设置 Express 中间件:

app.use(morgan('dev')); // log every request to the console
app.use(bodyParser.urlencoded({'extended':'true'})); // parse application/x-www-form-urlencoded
app.use(bodyParser.json()); // parse application/json
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
app.use(methodOverride());

治理 Mongoose 衔接

运用mongoose.connect将 MongoDB 和运用衔接,这会和数据库竖立衔接。这就是衔接 todoapi 数据库的最小操纵,数据库跑在当地,默许端口是 27017。假如当地衔接失利,尝尝将 localhost 换成 127.0.0.1。

有时刻当地主机名转变时会涌现一些问题。

//Connecting MongoDB using mongoose to our application
mongoose.connect(config.db);

//This callback will be triggered once the connection is successfully established to MongoDB
mongoose.connection.on('connected', function () {
  console.log('Mongoose default connection open to ' + config.db);
});

//Express application will listen to port mentioned in our configuration
app.listen(config.port, function(err){
  if(err) throw err;
  console.log("App listening on port "+config.port);
});

运用下面的敕令启动服务器:

//starting our node server
> node server.js
App listening on port 2000

为 API 编写测试用例

在 TDD(测试驱动开辟)中,将一切能够的输入、输出以及毛病归入斟酌,然后最先编写测试用例。来给我们的 Todo API 编写测试用例吧。

搭建测试环境

之前提到过,我们会运用 Mocha 作为测试运转器,Chai 作为断言库,用 Sinon.js 模仿 Todo model。起首装置单元测试环境:

> npm install mocha chai sinon sinon-mongoose --save

运用 sinon-mongoose 模块来模仿 Mongoose 定义的 MongoDB 模子。

如今,引入测试的依靠:

var sinon = require('sinon');
var chai = require('chai');
var expect = chai.expect;

var mongoose = require('mongoose');
require('sinon-mongoose');

//Importing our todo model for our unit testing.
var Todo = require('../../app/models/todo.model');

Todo API 的测试用例

编写单元测试时,须要同时斟酌胜利和失足的场景。

对我们的 Todo API 来讲,我们要给新建、删除、更新、查询 API 同时编写胜利和失足的测试用例。我们运用 Mocha, Chai 和 Sinon.js 来编写测试。

猎取一切 Todo

本小节,我们来编写从数据库猎取一切 todo 的测试用例。须要同时为胜利、失足场景编写,以确保代码在临盆中的种种环境下都能一般事情。

我们不会运用实在数据库来跑测试用例,而是用 sinon.mock 给 Todo schema 竖立假数据模子,然后再测试希冀的效果。

来运用 sinon.mock 给 Todo model 据,然后运用 find 要领猎取数据库中存储的一切 todo。

    describe("Get all todos", function(){
         // Test will pass if we get all todos
        it("should return all todos", function(done){
            var TodoMock = sinon.mock(Todo);
            var expectedResult = {status: true, todo: []};
            TodoMock.expects('find').yields(null, expectedResult);
            Todo.find(function (err, result) {
                TodoMock.verify();
                TodoMock.restore();
                expect(result.status).to.be.true;
                done();
            });
        });

        // Test will pass if we fail to get a todo
        it("should return error", function(done){
            var TodoMock = sinon.mock(Todo);
            var expectedResult = {status: false, error: "Something went wrong"};
            TodoMock.expects('find').yields(expectedResult, null);
            Todo.find(function (err, result) {
                TodoMock.verify();
                TodoMock.restore();
                expect(err.status).to.not.be.true;
                done();
            });
        });
    });

保留 New Todo

保留一个新的 todo,须要用一个示例使命来模仿 Todo model。运用我们竖立的Todo model来磨练 mongoose 的save 要领保留 todo 到数据库的效果。

    // Test will pass if the todo is saved
    describe("Post a new todo", function(){
        it("should create new post", function(done){
            var TodoMock = sinon.mock(new Todo({ todo: 'Save new todo from mock'}));
            var todo = TodoMock.object;
            var expectedResult = { status: true };
            TodoMock.expects('save').yields(null, expectedResult);
            todo.save(function (err, result) {
                TodoMock.verify();
                TodoMock.restore();
                expect(result.status).to.be.true;
                done();
            });
        });
        // Test will pass if the todo is not saved
        it("should return error, if post not saved", function(done){
            var TodoMock = sinon.mock(new Todo({ todo: 'Save new todo from mock'}));
            var todo = TodoMock.object;
            var expectedResult = { status: false };
            TodoMock.expects('save').yields(expectedResult, null);
            todo.save(function (err, result) {
                TodoMock.verify();
                TodoMock.restore();
                expect(err.status).to.not.be.true;
                done();
            });
        });
    });

依据 ID 更新 Todo

本节我们来磨练 API 的 update 功用。这和上面的例子很相似,除了我们要运用withArgs要领,模仿带有参数 ID 的 Todo model。

  // Test will pass if the todo is updated based on an ID
  describe("Update a new todo by id", function(){
    it("should updated a todo by id", function(done){
      var TodoMock = sinon.mock(new Todo({ completed: true}));
      var todo = TodoMock.object;
      var expectedResult = { status: true };
      TodoMock.expects('save').withArgs({_id: 12345}).yields(null, expectedResult);
      todo.save(function (err, result) {
        TodoMock.verify();
        TodoMock.restore();
        expect(result.status).to.be.true;
        done();
      });
    });
    // Test will pass if the todo is not updated based on an ID
    it("should return error if update action is failed", function(done){
      var TodoMock = sinon.mock(new Todo({ completed: true}));
      var todo = TodoMock.object;
      var expectedResult = { status: false };
      TodoMock.expects('save').withArgs({_id: 12345}).yields(expectedResult, null);
      todo.save(function (err, result) {
        TodoMock.verify();
        TodoMock.restore();
        expect(err.status).to.not.be.true;
        done();
      });
    });
  });

依据 ID 删除 Todo

这是 Todo API 单元测试的末了一小节。本节我们将基于给定的 ID ,运用 mongoose 的 remove 要领,测试 API 的 delete 功用。

    // Test will pass if the todo is deleted based on an ID
    describe("Delete a todo by id", function(){
        it("should delete a todo by id", function(done){
            var TodoMock = sinon.mock(Todo);
            var expectedResult = { status: true };
            TodoMock.expects('remove').withArgs({_id: 12345}).yields(null, expectedResult);
            Todo.remove({_id: 12345}, function (err, result) {
                TodoMock.verify();
                TodoMock.restore();
                expect(result.status).to.be.true;
                done();
            });
        });
        // Test will pass if the todo is not deleted based on an ID
        it("should return error if delete action is failed", function(done){
            var TodoMock = sinon.mock(Todo);
            var expectedResult = { status: false };
            TodoMock.expects('remove').withArgs({_id: 12345}).yields(expectedResult, null);
            Todo.remove({_id: 12345}, function (err, result) {
                TodoMock.verify();
                TodoMock.restore();
                expect(err.status).to.not.be.true;
                done();
            });
        });
    });

每次我们都要复原(restore) Todomock,确保下次它还能一般事情。

每次运转测试用例的时刻,一切的都邑失利,由于我们的临盆代码还没写好呢。我们会运转自动化测试,直至一切单元测试都经由历程。

> npm test

  Unit test for Todo API
    Get all todo
      1) should return all todo
      2) should return error
    Post a new todo
      3) should create new post
      4) should return error, if post not saved
    Update a new todo by id
      5) should updated a todo by id
      6) should return error if update action is failed
    Delete a todo by id
      7) should delete a todo by id
      8) should return error if delete action is failed

  0 passing (17ms)
  8 failing 

你在敕令行终端上运转npm test的时刻,会得到上面的输出信息,一切的测试用例都失利了。须要依据需乞降单元测试用例来编写运用逻辑,使我们的顺序越发稳固。

编写运用逻辑

下一步就是为 Todo API 编写真正的运用代码。我们会运转自动测试用例,一向重构,直到一切单元测试都经由历程。

设置路由

对客户端和服务端的 web 运用来讲,路由设置是最重要的一部份。在我们的运用中,运用 Express Router 的实例来处置惩罚一切路由。来给我们的运用竖立路由。

var express = require('express');
var router = express.Router();

var Todo = require('../models/todo.model');
var TodoController = require('../controllers/todo.controller')(Todo);

// Get all Todo
router.get('/todo', TodoController.GetTodo);

// Create new Todo
router.post('/todo', TodoController.PostTodo);

// Delete a todo based on :id
router.delete('/todo/:id', TodoController.DeleteTodo);

// Update a todo based on :id
router.put('/todo/:id', TodoController.UpdateTodo);

module.exports = router;

Controller(掌握器)

如今我们差不多在教程的末了阶段了,最先来写掌握器代码。在典范的 web 运用里,controller 掌握着保留、检索数据的重要逻辑,还要做考证。来写Todo API 真正的掌握器,运转自动化单元测试直至测试用例悉数经由历程。

    var Todo = require('../models/todo.model');

    var TodoCtrl = {
        // Get all todos from the Database
        GetTodo: function(req, res){
            Todo.find({}, function(err, todos){
              if(err) {
                res.json({status: false, error: "Something went wrong"});
                return;
              }
              res.json({status: true, todo: todos});
            });
        },
        //Post a todo into Database
        PostTodo: function(req, res){
            var todo = new Todo(req.body);
            todo.save(function(err, todo){
              if(err) {
                res.json({status: false, error: "Something went wrong"});
                return;
              }
              res.json({status: true, message: "Todo Saved!!"});
            });
        },
        //Updating a todo status based on an ID
        UpdateTodo: function(req, res){
            var completed = req.body.completed;
            Todo.findById(req.params.id, function(err, todo){
            todo.completed = completed;
            todo.save(function(err, todo){
              if(err) {
                res.json({status: false, error: "Status not updated"});
              }
              res.json({status: true, message: "Status updated successfully"});
            });
            });
        },
        // Deleting a todo baed on an ID
        DeleteTodo: function(req, res){
          Todo.remove({_id: req.params.id}, function(err, todos){
            if(err) {
              res.json({status: false, error: "Deleting todo is not successfull"});
              return;
            }
            res.json({status: true, message: "Todo deleted successfully!!"});
          });
        }
    }

module.exports = TodoCtrl;

运转测试用例

如今我们完成了运用的测试用例和掌握器逻辑两部份。来跑一下测试,看看终究效果:

> npm test
  Unit test for Todo API
    Get all todo
      ✓ should return all todo
      ✓ should return error
    Post a new todo
      ✓ should create new post
      ✓ should return error, if post not saved
    Update a new todo by id
      ✓ should updated a todo by id
      ✓ should return error if update action is failed
    Delete a todo by id
      ✓ should delete a todo by id
      ✓ should return error if delete action is failed

  8 passing (34ms) 

终究效果显现,我们一切的测试用例都经由历程了。接下来的步骤应当是 API 重构,这包含着重复本教程提到的雷同历程。

结论

经由历程本教程,我们进修了假如运用测试驱动开辟的方法,用 Node.js and MongoDB 设想 API。只管 TDD (测试驱动开辟)给开辟历程带来了分外复杂度,它能帮我们竖立更稳固的、毛病更少的运用。就算你不想实践 TDD, 最少也应当编写掩盖运用一切功用点的测试。

假如你有任何问题或主意,请不吝留言。

    原文作者:文蔺
    原文地址: https://segmentfault.com/a/1190000005869313
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞