मूल बातें
यूनिट परीक्षण में किसी को डीबी हिट नहीं करना चाहिए। मैं एक अपवाद के बारे में सोच सकता था:इन-मेमोरी डीबी को मारना, लेकिन यह भी पहले से ही एकीकरण परीक्षण के क्षेत्र में है क्योंकि आपको केवल जटिल प्रक्रियाओं के लिए स्मृति में सहेजे गए राज्य की आवश्यकता होगी (और इस प्रकार वास्तव में कार्यक्षमता की इकाइयां नहीं)। तो, हाँ नहीं वास्तविक डीबी।
यूनिट परीक्षणों में आप जो परीक्षण करना चाहते हैं, वह यह है कि आपके व्यावसायिक तर्क का परिणाम आपके एप्लिकेशन और डीबी के बीच इंटरफेस में सही एपीआई कॉल में होता है। आप और शायद यह मान सकते हैं कि डीबी एपीआई/ड्राइवर डेवलपर्स ने एक अच्छा काम परीक्षण किया है कि एपीआई के नीचे सब कुछ अपेक्षित व्यवहार करता है। हालाँकि, आप अपने परीक्षणों में यह भी शामिल करना चाहते हैं कि आपका व्यावसायिक तर्क विभिन्न मान्य API परिणामों पर कैसे प्रतिक्रिया करता है जैसे कि सफल बचत, डेटा स्थिरता के कारण विफलता, कनेक्शन समस्याओं के कारण विफलता आदि।
इसका मतलब यह है कि आपको जो चाहिए वह सब कुछ है जो डीबी ड्राइवर इंटरफेस के नीचे है। हालाँकि, आपको उस व्यवहार को मॉडल करने की आवश्यकता होगी ताकि डीबी कॉल के सभी परिणामों के लिए आपके व्यावसायिक तर्क का परीक्षण किया जा सके।
कहा से आसान है क्योंकि इसका मतलब है कि आपके द्वारा उपयोग की जाने वाली तकनीक के माध्यम से आपको एपीआई तक पहुंच की आवश्यकता है और आपको एपीआई जानने की जरूरत है।
नेवले की हकीकत
बुनियादी बातों पर टिके रहते हुए हम नेवले द्वारा उपयोग किए जाने वाले अंतर्निहित 'ड्राइवर' द्वारा की गई कॉलों का मजाक उड़ाना चाहते हैं। मान लें कि यह node-mongodb-native
है हमें उन कॉलों का मजाक उड़ाने की जरूरत है। नेवले और नेटिव ड्राइवर के बीच की पूरी बातचीत को समझना आसान नहीं है, लेकिन यह आमतौर पर mongoose.Collection
के तरीकों पर निर्भर करता है। क्योंकि बाद वाला mongoldb.Collection
. का विस्तार करता है और नहीं insert
. जैसी विधियों को फिर से लागू करें . अगर हम insert
. के व्यवहार को नियंत्रित करने में सक्षम हैं इस विशेष मामले में, तो हम जानते हैं कि हमने एपीआई स्तर पर डीबी एक्सेस का मजाक उड़ाया है। आप दोनों परियोजनाओं के स्रोत में इसका पता लगा सकते हैं, कि Collection.insert
वास्तव में मूल चालक विधि है।
आपके विशेष उदाहरण के लिए मैंने एक सार्वजनिक Git रिपॉजिटरी बनाया है। एक पूर्ण पैकेज के साथ, लेकिन मैं यहां सभी तत्वों को उत्तर में पोस्ट करूंगा।
समाधान
व्यक्तिगत रूप से मुझे नेवले के साथ काम करने का "अनुशंसित" तरीका काफी अनुपयोगी लगता है:मॉडल आमतौर पर मॉड्यूल में बनाए जाते हैं जहां संबंधित स्कीमा परिभाषित होते हैं, फिर भी उन्हें पहले से ही एक कनेक्शन की आवश्यकता होती है। एक ही प्रोजेक्ट में पूरी तरह से अलग-अलग मोंगोडब डेटाबेस से बात करने के लिए कई कनेक्शन रखने के उद्देश्य से और परीक्षण उद्देश्यों के लिए यह जीवन को वास्तव में कठिन बनाता है। वास्तव में, जैसे ही चिंताओं को पूरी तरह से अलग कर दिया जाता है, कम से कम मेरे लिए, लगभग अनुपयोगी हो जाता है।
तो पहली चीज जो मैं बनाता हूं वह है पैकेज विवरण फ़ाइल, एक स्कीमा वाला मॉड्यूल और एक सामान्य "मॉडल जनरेटर":
{
"name": "xxx",
"version": "0.1.0",
"private": true,
"main": "./src",
"scripts": {
"test" : "mocha --recursive"
},
"dependencies": {
"mongoose": "*"
},
"devDependencies": {
"mocha": "*",
"chai": "*"
}
}
var mongoose = require("mongoose");
var PostSchema = new mongoose.Schema({
title: { type: String },
postDate: { type: Date, default: Date.now }
}, {
timestamps: true
});
module.exports = PostSchema;
var model = function(conn, schema, name) {
var res = conn.models[name];
return res || conn.model.bind(conn)(name, schema);
};
module.exports = {
PostSchema: require("./post"),
model: model
};
इस तरह के एक मॉडल जनरेटर में इसकी कमियां हैं:ऐसे तत्व हैं जिन्हें मॉडल से जोड़ने की आवश्यकता हो सकती है और उन्हें उसी मॉड्यूल में रखना समझ में आता है जहां स्कीमा बनाया गया है। इसलिए उन्हें जोड़ने का एक सामान्य तरीका खोजना थोड़ा मुश्किल है। उदाहरण के लिए, एक मॉड्यूल पोस्ट-एक्शन को स्वचालित रूप से चलाने के लिए निर्यात कर सकता है जब किसी दिए गए कनेक्शन आदि के लिए मॉडल तैयार किया जाता है। (हैकिंग)।
अब एपीआई का मजाक उड़ाते हैं। मैं इसे सरल रखूंगा और प्रश्नगत परीक्षणों के लिए मुझे जो चाहिए, उसका केवल मजाक उड़ाऊंगा। यह जरूरी है कि मैं सामान्य रूप से एपीआई का मजाक उड़ाऊं, न कि अलग-अलग उदाहरणों के अलग-अलग तरीकों का। उत्तरार्द्ध कुछ मामलों में उपयोगी हो सकता है, या जब कुछ और मदद नहीं करता है, लेकिन मुझे अपने व्यावसायिक तर्क के अंदर बनाई गई वस्तुओं तक पहुंच की आवश्यकता होगी (जब तक कि इंजेक्शन या कुछ फैक्ट्री पैटर्न के माध्यम से प्रदान नहीं किया जाता), और इसका मतलब मुख्य स्रोत को संशोधित करना होगा। उसी समय, एक ही स्थान पर एपीआई का मजाक उड़ाने में एक खामी है:यह एक सामान्य समाधान है, जो संभवतः सफल निष्पादन को लागू करेगा। त्रुटि के मामलों के परीक्षण के लिए, परीक्षणों में उदाहरणों में खुद का मज़ाक उड़ाने की आवश्यकता हो सकती है, लेकिन फिर आपके व्यावसायिक तर्क के भीतर आपके पास उदाहरण के उदाहरण तक सीधी पहुंच नहीं हो सकती है। post
भीतर गहरे बनाया है।
तो, आइए सफल एपीआई कॉल का मजाक उड़ाने के सामान्य मामले पर एक नजर डालते हैं:
var mongoose = require("mongoose");
// this method is propagated from node-mongodb-native
mongoose.Collection.prototype.insert = function(docs, options, callback) {
// this is what the API would do if the save succeeds!
callback(null, docs);
};
module.exports = mongoose;
आम तौर पर, जब तक मॉडल बाद बनाए जाते हैं नेवले को संशोधित करते हुए, यह सोचा जा सकता है कि उपरोक्त मॉक किसी भी व्यवहार का अनुकरण करने के लिए प्रति परीक्षण के आधार पर किए जाते हैं। हालांकि, प्रत्येक परीक्षण से पहले मूल व्यवहार पर वापस जाना सुनिश्चित करें!
अंत में सभी संभावित डेटा बचत कार्यों के लिए हमारे परीक्षण इस तरह दिख सकते हैं। ध्यान दें, ये हमारे post
. के लिए विशिष्ट नहीं हैं मॉडल और अन्य सभी मॉडलों के लिए बिल्कुल समान मॉक के साथ किया जा सकता है।
// now we have mongoose with the mocked API
// but it is essential that our models are created AFTER
// the API was mocked, not in the main source!
var mongoose = require("./mock"),
assert = require("assert");
var underTest = require("../src");
describe("Post", function() {
var Post;
beforeEach(function(done) {
var conn = mongoose.createConnection();
Post = underTest.model(conn, underTest.PostSchema, "Post");
done();
});
it("given valid data post.save returns saved document", function(done) {
var post = new Post({
title: 'My test post',
postDate: Date.now()
});
post.save(function(err, doc) {
assert.deepEqual(doc, post);
done(err);
});
});
it("given valid data Post.create returns saved documents", function(done) {
var post = new Post({
title: 'My test post',
postDate: 876543
});
var posts = [ post ];
Post.create(posts, function(err, docs) {
try {
assert.equal(1, docs.length);
var doc = docs[0];
assert.equal(post.title, doc.title);
assert.equal(post.date, doc.date);
assert.ok(doc._id);
assert.ok(doc.createdAt);
assert.ok(doc.updatedAt);
} catch (ex) {
err = ex;
}
done(err);
});
});
it("Post.create filters out invalid data", function(done) {
var post = new Post({
foo: 'Some foo string',
postDate: 876543
});
var posts = [ post ];
Post.create(posts, function(err, docs) {
try {
assert.equal(1, docs.length);
var doc = docs[0];
assert.equal(undefined, doc.title);
assert.equal(undefined, doc.foo);
assert.equal(post.date, doc.date);
assert.ok(doc._id);
assert.ok(doc.createdAt);
assert.ok(doc.updatedAt);
} catch (ex) {
err = ex;
}
done(err);
});
});
});
यह ध्यान रखना आवश्यक है कि हम अभी भी बहुत निम्न स्तर की कार्यक्षमता का परीक्षण कर रहे हैं, लेकिन हम किसी भी व्यावसायिक तर्क का परीक्षण करने के लिए इसी दृष्टिकोण का उपयोग कर सकते हैं जो Post.create
का उपयोग करता है या post.save
आंतरिक रूप से।
बहुत ही अंतिम बिट, चलिए परीक्षण चलाते हैं:
> [email protected] test /Users/osklyar/source/web/xxx
> mocha --recursive
Post
✓ given valid data post.save returns saved document
✓ given valid data Post.create returns saved documents
✓ Post.create filters out invalid data
3 passing (52ms)
मुझे कहना होगा, इस तरह से ऐसा करने में कोई मज़ा नहीं है। लेकिन इस तरह यह वास्तव में बिना किसी मेमोरी या वास्तविक डीबी के व्यावसायिक तर्क का शुद्ध इकाई-परीक्षण है और काफी सामान्य है।