आवश्यकताएँ में यूनिट परीक्षण के लिए मैं कैसे निर्भरता का मजाक उड़ा सकता हूं?


127

मेरे पास एक एएमडी मॉड्यूल है जिसे मैं परीक्षण करना चाहता हूं, लेकिन मैं वास्तविक निर्भरता को लोड करने के बजाय इसकी निर्भरता का मजाक बनाना चाहता हूं। मैं आवश्यकताएँ का उपयोग कर रहा हूँ, और मेरे मॉड्यूल के लिए कोड कुछ इस तरह दिखता है:

define(['hurp', 'durp'], function(Hurp, Durp) {
  return {
    foo: function () {
      console.log(Hurp.beans)
    },
    bar: function () {
      console.log(Durp.beans)
    }
  }
}

मैं कैसे मॉक आउट कर सकता हूं hurpऔर durpइसलिए मैं प्रभावी रूप से इकाई परीक्षण कर सकता हूं?


मैं सिर्फ नोड में कुछ पागल eval सामान कर रहा हूँ define। हालांकि कुछ अलग विकल्प हैं। मैं आशाओं में उत्तर दूंगा कि यह मददगार होगा।
jergason

1
जैस्मीन के साथ इकाई परीक्षण के लिए आप जसक पर एक त्वरित नज़र डालना चाहते हैं । [डिस्क्लेमर: मैं
कामवासना

1
यदि आप नोड एनवी में परीक्षण कर रहे हैं, तो आप आवश्यकता-नकली पैकेज का उपयोग कर सकते हैं । यह आपको आसानी से अपनी निर्भरता को बदलने, मॉड्यूल को बदलने आदि की अनुमति देता है। यदि आपको async मॉड्यूल लोड के साथ ब्राउज़र की आवश्यकता है - तो आप Squire.js
ValeriiVasin

जवाबों:


64

इसलिए इस पोस्ट को पढ़ने के बाद मैं एक समाधान के साथ आया, जो आपके परीक्षण के लिए एक नया संदर्भ बनाने के लिए रिक्जेस्ट कॉन्फिग फ़ंक्शन का उपयोग करता है, जहाँ आप अपनी निर्भरता का मज़ाक उड़ा सकते हैं:

var cnt = 0;
function createContext(stubs) {
  cnt++;
  var map = {};

  var i18n = stubs.i18n;
  stubs.i18n = {
    load: sinon.spy(function(name, req, onLoad) {
      onLoad(i18n);
    })
  };

  _.each(stubs, function(value, key) {
    var stubName = 'stub' + key + cnt;

    map[key] = stubName;

    define(stubName, function() {
      return value;
    });
  });

  return require.config({
    context: "context_" + cnt,
    map: {
      "*": map
    },
    baseUrl: 'js/cfe/app/'
  });
}

तो यह एक नया संदर्भ में, जहां के लिए परिभाषाएँ बनाता है Hurpऔर Durpवस्तुओं आप समारोह में पारित कर दिया द्वारा निर्धारित किया जाएगा। नाम के लिए Math.random शायद थोड़ा गंदा है लेकिन यह काम करता है। यदि आपके पास परीक्षण का एक समूह है, तो इसका कारण यह है कि आपको अपने सूट का पुन: उपयोग करने से रोकने के लिए, या जब आप वास्तविक आवश्यकता मॉड्यूल चाहते हैं, तो लोड को रोकने के लिए हर सूट के लिए नया संदर्भ बनाने की आवश्यकता है।

आपके मामले में यह इस तरह दिखेगा:

(function () {

  var stubs =  {
    hurp: 'hurp',
    durp: 'durp'
  };
  var context = createContext(stubs);

  context(['yourModuleName'], function (yourModule) {

    //your normal jasmine test starts here

    describe("yourModuleName", function () {
      it('should log', function(){
         spyOn(console, 'log');
         yourModule.foo();

         expect(console.log).toHasBeenCalledWith('hurp');
      })
    });
  });
})();

इसलिए मैं थोड़ी देर के लिए उत्पादन में इस दृष्टिकोण का उपयोग कर रहा हूं और वास्तव में मजबूत हूं।


1
मुझे पसंद है कि आप यहां क्या कर रहे हैं ... खासकर जब से आप प्रत्येक परीक्षण के लिए एक अलग संदर्भ लोड कर सकते हैं। केवल एक चीज जो मैं चाहता हूं कि मैं बदल सकता हूं, ऐसा लगता है कि यह केवल काम कर रहा है अगर मैं सभी निर्भरताओं का मजाक उड़ाता हूं। क्या आपको पता है कि अगर वे हैं, तो नकली वस्तुओं को वापस करने का एक तरीका है, लेकिन अगर नकली नहीं दिया जाता है तो वास्तविक .js फ़ाइल से पुनर्प्राप्त करने के लिए वापसी? मैं यह पता लगाने के लिए आवश्यकता कोड के माध्यम से खुदाई करने की कोशिश कर रहा हूं, लेकिन मैं थोड़ा खो रहा हूं।
ग्लेन ह्यूजेस

5
यह केवल उस निर्भरता का मजाक उड़ाता है जिसे आप createContextफ़ंक्शन में पास करते हैं। तो आपके मामले में यदि आप केवल {hurp: 'hurp'}कार्य करने के लिए गुजरते हैं, तो durpफ़ाइल को सामान्य निर्भरता के रूप में लोड किया जाएगा।
एंड्रियास कोबर्ले

1
मैं रेल्स (jasminerice / phantomjs के साथ) में इसका उपयोग कर रहा हूं और यह सबसे अच्छा समाधान है जिसे मैंने आवश्यकताएँ के साथ मजाक करने के लिए पाया है।
बेन एंडरसन

13
+1 सुंदर नहीं है, लेकिन सभी संभावित समाधानों में यह सबसे कम बदसूरत / गन्दा लगता है। यह समस्या अधिक ध्यान देने योग्य है।
क्रिस सैल्जबर्ग

1
अद्यतन: इस समाधान पर विचार करने वाले किसी व्यक्ति के लिए, मैं नीचे उल्लिखित squire.js ( github.com/iammerrick/Squire.js ) की जाँच करने का सुझाव दूंगा । यह एक समाधान का एक अच्छा कार्यान्वयन है जो इस के समान है, जहां भी स्टब्स की आवश्यकता होती है, नए संदर्भों का निर्माण करना।
क्रिस सैल्जबर्ग

44

आप नए स्क्वायर की जाँच करना चाहते हैं

डॉक्स से:

Squire.js आवश्यकताएँ के लिए एक निर्भरता इंजेक्टर है। उपयोगकर्ताओं को निर्भरता को आसान बनाने के लिए।


2
जोरदार सिफारिश! मैं squire.js का उपयोग करने के लिए अपने कोड को अपडेट कर रहा हूं और अब तक मैं इसे बहुत पसंद कर रहा हूं। बहुत सरल कोड, हुड के नीचे कोई महान जादू नहीं है, लेकिन इस तरह से किया जाता है जो (अपेक्षाकृत) समझने में आसान है।
क्रिस सैल्जबर्ग

1
मुझे अन्य परीक्षणों को प्रभावित करने वाली स्क्वॉयर साइड की बहुत सारी समस्याएं हैं और मैं इसकी सिफारिश नहीं कर सकता। मैं npmjs.com/package/requirejs-mock
जेफ व्हिटिंग

17

मुझे इस समस्या के तीन अलग-अलग समाधान मिले हैं, उनमें से कोई भी सुखद नहीं है।

निर्भरता को परिभाषित करना इनलाइन

define('hurp', [], function () {
  return {
    beans: 'Beans'
  };
});

define('durp', [], function () {
  return {
    beans: 'durp beans'
  };
});

require('hurpdhurp', function () {
  // test hurpdurp in here
});

फगली। आपको बहुत सारे एएमडी बॉयलरप्लेट के साथ अपने परीक्षणों को बंद करना होगा।

विभिन्न रास्तों से मॉक डिपेंडेंसी लोड हो रही है

इसमें प्रत्येक निर्भरता के लिए पथ को परिभाषित करने के लिए एक अलग config.js फ़ाइल का उपयोग करना शामिल है जो मूल निर्भरता के बजाय मॉक को इंगित करता है। यह भी बदसूरत है, परीक्षण फ़ाइलों और कॉन्फ़िगरेशन फ़ाइलों के टन के निर्माण की आवश्यकता है।

नोड में यह नकली

यह मेरा वर्तमान समाधान है, लेकिन अभी भी एक भयानक है।

आप defineमॉड्यूल को अपने स्वयं के मोज़ेक प्रदान करने के लिए अपना स्वयं का फ़ंक्शन बनाते हैं और कॉलबैक में अपने परीक्षण डालते हैं। फिर आप evalअपने परीक्षणों को चलाने के लिए मॉड्यूल, जैसे:

var fs = require('fs')
  , hurp = {
      beans: 'BEANS'
    }
  , durp = {
      beans: 'durp beans'
    }
  , hurpDurp = fs.readFileSync('path/to/hurpDurp', 'utf8');
  ;



function define(deps, cb) {
  var TestableHurpDurp = cb(hurp, durp);
  // now run tests below on TestableHurpDurp, which is using your
  // passed-in mocks as dependencies.
}

// evaluate the AMD module, running your mocked define function and your tests.
eval(hurpDurp);

यह मेरा पसंदीदा उपाय है। यह थोड़ा जादू दिखता है, लेकिन इसके कुछ फायदे हैं।

  1. नोड में अपने परीक्षण चलाएं, ताकि ब्राउज़र स्वचालन के साथ कोई खिलवाड़ न हो।
  2. अपने परीक्षणों में गड़बड़ एएमडी बॉयलरप्लेट की कम आवश्यकता।
  3. आप evalक्रोध में उपयोग करने के लिए , और क्रोध के साथ क्रॉकफोर्ड विस्फोट की कल्पना करते हैं।

यह अभी भी कुछ कमियां है, जाहिर है।

  1. चूंकि आप नोड में परीक्षण कर रहे हैं, आप ब्राउज़र की घटनाओं या DOM हेरफेर के साथ कुछ नहीं कर सकते। केवल परीक्षण तर्क के लिए अच्छा है।
  2. अभी भी थोडा गुदगुदाया है। आपको defineहर परीक्षा में मॉक आउट करने की आवश्यकता है , क्योंकि यह वह जगह है जहाँ आपके परीक्षण वास्तव में चलते हैं।

मैं इस तरह के सामान के लिए एक अच्छे सिंटैक्स देने के लिए एक परीक्षण धावक पर काम कर रहा हूं, लेकिन मेरे पास अभी भी समस्या 1 का कोई अच्छा समाधान नहीं है।

निष्कर्ष

आवश्यकताएँ में Mocking डिप्स कठिन बेकार है। मुझे एक रास्ता मिला जो कि सॉर्टो काम करता है, लेकिन मैं अभी भी इससे बहुत खुश नहीं हूं। कृपया मुझे बताएं कि क्या आपके पास कोई बेहतर विचार है।


15

एक config.mapविकल्प है http://requirejs.org/docs/api.html#config-map

इसका उपयोग कैसे करें:

  1. सामान्य मॉड्यूल को परिभाषित करें;
  2. स्टब मॉड्यूल को परिभाषित करें;
  3. आवश्यकता के अनुरूप कॉन्फ़िगर करें;

    requirejs.config({
      map: {
        'source/js': {
          'foo': 'normalModule'
        },
        'source/test': {
          'foo': 'stubModule'
        }
      }
    });

सामान्य और परीक्षण कोड के लिए इस मामले में आप fooमॉड्यूल का उपयोग कर सकते हैं जो वास्तविक मॉड्यूल संदर्भ होगा और तदनुसार स्टब होगा।


इस दृष्टिकोण ने मेरे लिए वास्तव में अच्छा काम किया। मेरे मामले में, मैंने इसे परीक्षण धावक पृष्ठ के HTML में जोड़ा -> नक्शा: {'*': {'सामान्य / मॉड्यूल / उपयोगीमॉड्यूल': '/ tests/Specs/Common/usefulModulebock.js'}}
संरेखित

9

आप testr.js का उपयोग निर्भरता का मज़ाक उड़ाने के लिए कर सकते हैं । आप मूल के बजाय नकली निर्भरता को लोड करने के लिए टेस्टर सेट कर सकते हैं। यहाँ एक उदाहरण है:

var fakeDep = function(){
    this.getText = function(){
        return 'Fake Dependancy';
    };
};

var Module1 = testr('module1', {
    'dependancies/dependancy1':fakeDep
});

इसे भी देखें: http://cyberasylum.janithw.com/mocking-requirejs-d dependencies-for-unit-testing/


2
मैं वास्तव में काम करने के लिए testr.js चाहता था, लेकिन यह अभी तक कार्य को महसूस नहीं करता है। अंत में मैं @Andreas Köberle के समाधान के साथ जा रहा हूं, जो मेरे परीक्षणों में नेस्टेड संदर्भों को जोड़ देगा (सुंदर नहीं) लेकिन जो लगातार काम करता है। काश कोई इस समाधान को और अधिक सुरुचिपूर्ण तरीके से हल करने पर ध्यान केंद्रित कर सकता। मैं testr.js देखता रहूंगा और यदि / जब यह काम करता है, तो स्विच कर देगा।
क्रिस सल्जबर्ग

@ श्याओमा हाय, प्रतिक्रिया के लिए धन्यवाद! मुझे यह देखने में खुशी होगी कि आपने अपने परीक्षण स्टैक के भीतर testr.js को कैसे कॉन्फ़िगर किया है। आपके द्वारा किए जा रहे किसी भी मुद्दे को ठीक करने में मदद करने में प्रसन्नता! अगर आप वहां कुछ लॉग इन करना चाहते हैं तो जीथब इश्यूज पेज भी है। धन्यवाद,
मैटी एफ

1
@MattyF क्षमा करें, मुझे अभी याद नहीं है कि सही कारण क्या था कि testr.js ने मेरे लिए काम नहीं किया, लेकिन मैं इस निष्कर्ष पर पहुंचा हूं कि अतिरिक्त संदर्भों का उपयोग वास्तव में काफी ठीक है और वास्तव में लाइन में है आवश्यकता के साथ.जेएस का उपयोग मॉकिंग / स्टबिंग के लिए किया जाना था।
क्रिस सैल्जबर्ग

2

यह उत्तर एंड्रियास कोबर्ले के उत्तर पर आधारित है ।
मेरे लिए यह आसान नहीं था कि मैं उनके समाधान को लागू करूं और समझ सकूं, इसलिए मैं इसे थोड़ा और विस्तार से बताऊंगा कि यह कैसे काम करता है, और कुछ नुकसान से बचने के लिए, उम्मीद है कि यह भविष्य के आगंतुकों की मदद करेगा।

तो, सबसे पहले सेटअप:
मैं कर्मा को टेस्ट रनर के रूप में इस्तेमाल कर रहा हूं और टेस्ट फ्रेमवर्क के रूप में मोचाज

स्क्वॉयर जैसी किसी चीज़ का उपयोग करना मेरे लिए किसी कारण से काम नहीं आया, जब मैंने इसका उपयोग किया, तो परीक्षण के ढांचे में त्रुटियां थीं:

TypeError: अपरिभाषित की संपत्ति 'कॉल' नहीं पढ़ सकता है

RequireJs संभावना के लिए है नक्शा अन्य मॉड्यूल आईडी के लिए मॉड्यूल आईडी। यह एक requireफ़ंक्शन बनाने की अनुमति देता है जो वैश्विक की तुलना में एक अलग कॉन्फ़िगरेशन का उपयोग करता है require
इस समाधान के काम करने के लिए ये विशेषताएं महत्वपूर्ण हैं।

यहाँ मॉक कोड का मेरा संस्करण है, जिसमें (बहुत) टिप्पणियां शामिल हैं (मुझे उम्मीद है कि यह समझ में आएगा)। मैंने इसे एक मॉड्यूल के अंदर लपेट दिया, ताकि परीक्षणों को आसानी से इसकी आवश्यकता हो।

define([], function () {
    var count = 0;
    var requireJsMock= Object.create(null);
    requireJsMock.createMockRequire = function (mocks) {
        //mocks is an object with the module ids/paths as keys, and the module as value
        count++;
        var map = {};

        //register the mocks with unique names, and create a mapping from the mocked module id to the mock module id
        //this will cause RequireJs to load the mock module instead of the real one
        for (property in mocks) {
            if (mocks.hasOwnProperty(property)) {
                var moduleId = property;  //the object property is the module id
                var module = mocks[property];   //the value is the mock
                var stubId = 'stub' + moduleId + count;   //create a unique name to register the module

                map[moduleId] = stubId;   //add to the mapping

                //register the mock with the unique id, so that RequireJs can actually call it
                define(stubId, function () {
                    return module;
                });
            }
        }

        var defaultContext = requirejs.s.contexts._.config;
        var requireMockContext = { baseUrl: defaultContext.baseUrl };   //use the baseUrl of the global RequireJs config, so that it doesn't have to be repeated here
        requireMockContext.context = "context_" + count;    //use a unique context name, so that the configs dont overlap
        //use the mapping for all modules
        requireMockContext.map = {
            "*": map
        };
        return require.config(requireMockContext);  //create a require function that uses the new config
    };

    return requireJsMock;
});

सबसे बड़ी गड़बड़ी जिसका मुझे सामना करना पड़ा, जिसका शाब्दिक अर्थ है कि मुझे घंटों का समय देना पड़ता है। मैंने इसे (गहरी) कॉपी करने की कोशिश की, और केवल आवश्यक गुणों (जैसे संदर्भ या मानचित्र) को ओवरराइड किया। यह काम नहीं करता! केवल कॉपी करें baseUrl, यह ठीक काम करता है।

प्रयोग

इसका उपयोग करने के लिए, इसे अपने परीक्षण में आवश्यकता होती है, मोक्स बनाते हैं, और फिर इसे पास करते हैं createMockRequire। उदाहरण के लिए:

var ModuleMock = function () {
    this.method = function () {
        methodCalled += 1;
    };
};
var mocks = {
    "ModuleIdOrPath": ModuleMock
}
var requireMocks = mocker.createMockRequire(mocks);

और यहाँ एक पूर्ण परीक्षण फ़ाइल का एक उदाहरण है :

define(["chai", "requireJsMock"], function (chai, requireJsMock) {
    var expect = chai.expect;

    describe("Module", function () {
        describe("Method", function () {
            it("should work", function () {
                return new Promise(function (resolve, reject) {
                    var handler = { handle: function () { } };

                    var called = 0;
                    var moduleBMock = function () {
                        this.method = function () {
                            methodCalled += 1;
                        };
                    };
                    var mocks = {
                        "ModuleBIdOrPath": moduleBMock
                    }
                    var requireMocks = requireJsMock.createMockRequire(mocks);

                    requireMocks(["js/ModuleA"], function (moduleA) {
                        try {
                            moduleA.method();   //moduleA should call method of moduleBMock
                            expect(called).to.equal(1);
                            resolve();
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
            });
        });
    });
});

0

यदि आप कुछ सादे जेएस परीक्षण करना चाहते हैं जो एक इकाई को अलग करते हैं, तो आप बस इस स्निपेट का उपयोग कर सकते हैं:

function define(args, func){
    if(!args.length){
        throw new Error("please stick to the require.js api which wants a: define(['mydependency'], function(){})");
    }

    var fileName = document.scripts[document.scripts.length-1].src;

    // get rid of the url and path elements
    fileName = fileName.split("/");
    fileName = fileName[fileName.length-1];

    // get rid of the file ending
    fileName = fileName.split(".");
    fileName = fileName[0];

    window[fileName] = func;
    return func;
}
window.define = define;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.