摘要:首先安裝單元測試環境使用模塊來模擬定義的模型。根據刪除這是單元測試的最后一小節。需要根據需求和單元測試用例來編寫應用邏輯,使我們的程序更加穩定。我們會運行自動測試用例,一直重構,直到所有單元測試都通過。
本文轉載自:眾成翻譯
譯者:文藺
鏈接: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 有自己的包管理工具 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, 至少也應該編寫覆蓋應用所有功能點的測試。
如果你有任何問題或想法,請不吝留言。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/18872.html
摘要:首先安裝單元測試環境使用模塊來模擬定義的模型。根據刪除這是單元測試的最后一小節。需要根據需求和單元測試用例來編寫應用邏輯,使我們的程序更加穩定。我們會運行自動測試用例,一直重構,直到所有單元測試都通過。 本文轉載自:眾成翻譯譯者:文藺鏈接:http://www.zcfy.cc/article/746原文:https://semaphoreci.com/community/tutoria...
摘要:使用訪問后端服務使用的美化組件的方法整合全棧服務其中的指的是。所幸是在這個教程內,你不需要學習太多就可以把案例跑起來。另外一個組件負責顯示全部項目,并接受刪除事件,刪除指定的項目。它們分別是組件和組件這兩個組件的代碼實現,分別在文件和內。 自從一年前發布了Vuejs小書的電子書,也有些日子沒有碰過它們了,現在因為項目的緣故,需要使用JavaScript全棧開發。所以,我得把這個全棧環境...
摘要:使用訪問后端服務使用的美化組件的方法整合全棧服務其中的指的是。所幸是在這個教程內,你不需要學習太多就可以把案例跑起來。另外一個組件負責顯示全部項目,并接受刪除事件,刪除指定的項目。它們分別是組件和組件這兩個組件的代碼實現,分別在文件和內。 自從一年前發布了Vuejs小書的電子書,也有些日子沒有碰過它們了,現在因為項目的緣故,需要使用JavaScript全棧開發。所以,我得把這個全棧環境...
摘要:也是一款優秀的響應式框架站點所使用的一套框架為微信服務量身設計的一套框架一組很小的,響應式的組件,你可以在網頁的項目上到處使用一個可定制的文件,使瀏覽器呈現的所有元素,更一致和符合現代標準。 GitHub 值得收藏的前端項目 整理與收集的一些比較優秀github項目,方便自己閱讀,順便分享出來,大家一起學習,本篇文章會持續更新,版權歸原作者所有。歡迎github star與fork 預...
閱讀 2162·2023-04-26 00:43
閱讀 2680·2021-11-22 15:22
閱讀 3809·2021-11-11 16:55
閱讀 966·2021-11-04 16:06
閱讀 1782·2019-08-30 14:12
閱讀 994·2019-08-30 14:02
閱讀 3366·2019-08-29 17:05
閱讀 1415·2019-08-29 12:27