簡體   English   中英

業力覆蓋范圍+ RequireJS:誤導覆蓋率報告

[英]Karma Coverage + RequireJS: Misleading coverage reports

今天我將Karma Coverage集成到我現有的RequireJS應用程序中。 我添加了karma-requirejs插件,我能夠成功獲得覆蓋率報告。

最初報告非常好,幾乎100%覆蓋。 當我仔細分析結果時,我注意到我的“src”文件夾中有很多丟失的文件,這導致了這個非常好的報告。

事實證明,覆蓋范圍只是對具有相應規范的“src”文件應用分析(因為我在該規范中使用require ('someFileFromSrcFolder'))。

問題 :覆蓋分析是否有辦法分析“src”文件夾中的所有文件?

噶conf.js

module.exports = function (config) {
    config.set({
        basePath: '../',
        autoWatch: true,
        singleRun: false,
        frameworks: ['jasmine', 'requirejs'],
        browsers: ['PhantomJS', 'Chrome'],
        logLevel: config.LOG_ERROR,
        files: [
            {pattern: 'vendor/**/*.js', included: false},
            {pattern: 'vendor/**/*.html', included: false},
            {pattern: 'src/**/*.js', included: false},
            {pattern: 'src/**/*.html', included: false},
            {pattern: 'tests/mock/**/*.js', included: false},
            {pattern: 'tests/**/*Specs.js', included: false},

            'tests/test-require.js'
        ],

        // list of files to exclude
        exclude: [
            'src/app-require.js'
        ],

        reporters: ['progress', 'coverage'],

        preprocessors: {
            'src/**/*.js': ['coverage']
        }
    });
};

測試require.js

var allTestFiles = [];
var TEST_REGEXP = /Specs\.js$/;

Object.keys(window.__karma__.files).forEach(function (file) {
    if (TEST_REGEXP.test(file)) {
        allTestFiles.push(file);
    } 
});

require.config({

    baseUrl: '/base/',

    paths: {
       ...
    },

    deps: allTestFiles,

    callback: window.__karma__.start(),

waitSeconds: 20
});

好,

嘗試了一下后,我能夠找到一個似乎可以解決問題的解決方案。 我仍然不確定這是否是最好的解決方案,但我會在這里發布我所做的事情,以便收集您的反饋意見。

基本上,我必須更改test-require.js中的加載機制,以包含默認情況下的所有包。 更新的test-require.js應如下所示:

更新了Test-require.js

var allTestFiles = [];
var modules = [];
var TEST_REGEXP = /Specs\.js$/;
var SRC_REGEXP = /src\//;
var JS_REGEXP = /\.js$/;

/**
* This function converts a given js path to requirejs module
*/
var jsToModule = function (path) {
    return path.replace(/^\/base\//, '').replace(/\.js$/, '');
};

Object.keys(window.__karma__.files).forEach(function (file) {
    if (TEST_REGEXP.test(file)) {
        allTestFiles.push(file);
    } else if (SRC_REGEXP.test(file) && JS_REGEXP.test(file)) {
        modules.push(jsToModule(file));
    }
});

var startTest = function () {
    //loading all the existing requirejs src modules before
    //triggering the karma test
    require(modules, function () { 
        window.__karma__.start();
    });
};

require.config({

    baseUrl: '/base/',

    paths: {
       ....
    },

    // dynamically load all test files
    deps: allTestFiles,

    callback: startTest,

    waitSeconds: 20
});

現在,當我運行測試測試時,覆蓋范圍包括所有src模塊,我終於可以獲得一致且准確的報告。

我最近有同樣的問題。 我在我的項目中有一個解決方案angular-require-lazy ,我將對此進行描述。 雖然它需要很多自定義的東西,但它最終會起作用。

摘要

  1. 預先檢測代碼,保持基線
  2. 使Karma服務器發送預先檢測的源
  3. 使用記錄器收集覆蓋率結果,該記者將基線考慮在內

1.預先檢測

首先,我使用coverage預處理。 由於RequireJS動態加載源,現有的預處理器不足以滿足我們的需求。 相反,我首先使用自定義Grunt插件手動運行伊斯坦布爾的儀器階段:

在這里查看

module.exports = function(grunt) {
    grunt.registerMultiTask("instrument", "Instrument with istanbul", function() {
        var istanbul = require("istanbul"),
            instrumenter,
            options,
            instrumenterOptions,
            baselineCollector;

        options = this.options({
        });

        if( options.baseline ) {
            baselineCollector = new istanbul.Collector();
        }

        instrumenterOptions = {
            coverageVariable: options.coverageVariable || "__coverage__",
            embedSource: options.embedSource || false,
            preserveComments: options.preserveComments || false,
            noCompact: options.noCompact || false,
            noAutoWrap: options.noAutoWrap || false,
            codeGenerationOptions: options.codeGenerationOptions,
            debug: options.debug || false,
            walkDebug: options.walkDebug || false
        };

        instrumenter = new istanbul.Instrumenter(instrumenterOptions);

        this.files.forEach(function(f) {
            if( f.src.length !== 1 ) {
                throw new Error("encountered src with length: " + f.src.length + ": " + JSON.stringify(f.src));
            }
            var filename = f.src[0],
                code = grunt.file.read(filename, {encoding: grunt.file.defaultEncoding}),
                result = instrumenter.instrumentSync(code, filename),
                baseline,
                coverage;

            if( options.baseline ) {
                baseline = instrumenter.lastFileCoverage();
                coverage = {};
                coverage[baseline.path] = baseline;
                baselineCollector.add(coverage);
            }

            grunt.file.write(f.dest, result, {encoding: grunt.file.defaultEncoding});
        });

        if( options.baseline ) {
            grunt.file.write(options.baseline, JSON.stringify(baselineCollector.getFinalCoverage()), {encoding: grunt.file.defaultEncoding});
        }
    });
};

它用作:

grunt.initConfig({
    instrument: {
        sources: {
            files: [{
                expand: true,
                cwd: "..the base directory of your sources...",
                src: ["...all your sources..."],
                dest: "...where to put the instrumented files..."
            }],
            options: {
                baseline: "build-coverage/baseline.json" // IMPORTANT!
            }
        }
    },
    ...

保持基線以供日后使用非常重要。

如果不使用Grunt,我認為您仍然可以從此代碼中獲取想法。 實際上,伊斯坦布爾API非常適合手動工作,所以如果需要,請繼續使用它。

2.配置Karma服務器以發送預先檢測的文件

對於初學者,配置預處理器以使用預先檢測的文件( 注意我們將使用自定義預處理器報告器,代碼在最后):

    ...
    preprocessors: {
        '...all your sources...': 'preInstrumented'
    },

    preInstrumentedPreprocessor: {
        basePath: '!!!SAME AS GRUNT'S dest!!!',
        stripPrefix: '...the base prefix to strip, same as Grunt's cwd...'
    },
    ...

3.調整記者使用基線

報道記者必須考慮基線。 不幸的是,原始的沒有,所以我稍微調整了一下。 配置是:

    ...
    reporters: [
        'progress', 'coverage'
    ],

    coverageReporter: {
        type: 'lcov',
        dir: 'build-coverage/report',
        baseLine: '!!!SAME AS GRUNT'S options.baseline!!!'
    },
    ...

要激活我的自定義Karma插件,我包括:

    plugins: [
        ...
        require('./build-scripts/karma')
    ],

文件夾./build-scripts/karma包含以下文件:

index.js

module.exports = {
    "preprocessor:preInstrumented": ["factory", require("./preInstrumentedPreprocessor")],
    "reporter:coverage": ["type", require("./reporter")]
};

preInstrumentedPreprocessor.js

var path = require("path"),
    fs = require("fs");

createPreInstrumentedPreprocessor.$inject = ["args", "config.preInstrumentedPreprocessor", "config.basePath", "logger", "helper"];
function createPreInstrumentedPreprocessor(args, config, basePath, logger, helper) {
    var STRIP_PREFIX_RE = new RegExp("^" + path.join(basePath, config.stripPrefix).replace(/\\/g, "\\\\"));

    function instrumentedFilePath(file) {
        return path.join(basePath, config.basePath, path.normalize(file.originalPath).replace(STRIP_PREFIX_RE, ""));
    }

    return function(content, file, done) {
        fs.readFile(instrumentedFilePath(file), {encoding:"utf8"}, function(err, instrumentedContent) {
            if( err ) throw err;
            done(instrumentedContent);
        });
    };
}

module.exports = createPreInstrumentedPreprocessor;

記者.js

(查看這個問題的原因讓我“岔開”它。)

// DERIVED FROM THE COVERAGE REPORTER OF KARMA, https://github.com/karma-runner/karma-coverage/blob/master/lib/reporter.js
var path = require('path');
var fs = require('fs');
var util = require('util');
var istanbul = require('istanbul');
var dateformat = require('dateformat');


var Store = istanbul.Store;

var BasePathStore = function(opts) {
    Store.call(this, opts);
    opts = opts || {};
    this.basePath = opts.basePath;
    this.delegate = Store.create('fslookup');
};
BasePathStore.TYPE = 'basePathlookup';
util.inherits(BasePathStore, Store);

Store.mix(BasePathStore, {
    keys : function() {
    return this.delegate.keys();
    },
    toKey : function(key) {
    if (key.indexOf('./') === 0) { return path.join(this.basePath, key); }
    return key;
    },
    get : function(key) {
    return this.delegate.get(this.toKey(key));
    },
    hasKey : function(key) {
    return this.delegate.hasKey(this.toKey(key));
    },
    set : function(key, contents) {
    return this.delegate.set(this.toKey(key), contents);
    }
});


// TODO(vojta): inject only what required (config.basePath, config.coverageReporter)
var CoverageReporter = function(rootConfig, helper, logger) {
    var log = logger.create('coverage');
    var config = rootConfig.coverageReporter || {};
    var basePath = rootConfig.basePath;
    var reporters = config.reporters;
    var baseLine;

    if (config.baseLine) {
    baseLine = JSON.parse(fs.readFileSync(path.join(basePath, config.baseLine), {encoding:"utf8"}));
    }

    if (!helper.isDefined(reporters)) {
    reporters = [config];
    }

    this.adapters = [];
    var collectors;
    var pendingFileWritings = 0;
    var fileWritingFinished = function() {};

    function writeEnd() {
    if (!--pendingFileWritings) {
        // cleanup collectors
        Object.keys(collectors).forEach(function(key) {
        collectors[key].dispose();
        });
        fileWritingFinished();
    }
    }

    /**
    * Generate the output directory from the `coverageReporter.dir` and
    * `coverageReporter.subdir` options.
    *
    * @param {String} browserName - The browser name
    * @param {String} dir - The given option
    * @param {String|Function} subdir - The given option
    *
    * @return {String} - The output directory
    */
    function generateOutputDir(browserName, dir, subdir) {
    dir = dir || 'coverage';
    subdir = subdir || browserName;

    if (typeof subdir === 'function') {
        subdir = subdir(browserName);
    }

    return path.join(dir, subdir);
    }

    this.onRunStart = function(browsers) {
    collectors = Object.create(null);

    // TODO(vojta): remove once we don't care about Karma 0.10
    if (browsers) {
        browsers.forEach(function(browser) {
        collectors[browser.id] = new istanbul.Collector();
        });
    }
    };

    this.onBrowserStart = function(browser) {
    var collector = new istanbul.Collector();
    if( baseLine ) {
        collector.add(baseLine);
    }
    collectors[browser.id] = collector;
    };

    this.onBrowserComplete = function(browser, result) {
    var collector = collectors[browser.id];

    if (!collector) {
        return;
    }

    if (result && result.coverage) {
        collector.add(result.coverage);
    }
    };

    this.onSpecComplete = function(browser, result) {
    if (result.coverage) {
        collectors[browser.id].add(result.coverage);
    }
    };

    this.onRunComplete = function(browsers) {
    reporters.forEach(function(reporterConfig) {
        browsers.forEach(function(browser) {

        var collector = collectors[browser.id];
        if (collector) {
            pendingFileWritings++;

            var outputDir = helper.normalizeWinPath(path.resolve(basePath, generateOutputDir(browser.name,
                                                                                            reporterConfig.dir || config.dir,
                                                                                            reporterConfig.subdir || config.subdir)));

            helper.mkdirIfNotExists(outputDir, function() {
            log.debug('Writing coverage to %s', outputDir);
            var options = helper.merge({}, reporterConfig, {
                dir : outputDir,
                sourceStore : new BasePathStore({
                basePath : basePath
                })
            });
            var reporter = istanbul.Report.create(reporterConfig.type || 'html', options);
            try {
                reporter.writeReport(collector, true);
            } catch (e) {
                log.error(e);
            }
            writeEnd();
            });
        }

        });
    });
    };

    this.onExit = function(done) {
    if (pendingFileWritings) {
        fileWritingFinished = done;
    } else {
        done();
    }
    };
};

CoverageReporter.$inject = ['config', 'helper', 'logger'];

// PUBLISH
module.exports = CoverageReporter;

我知道這是很多代碼。 我希望有一個更簡單的解決方案(任何人的想法?)。 無論如何,你可以看看它如何工作與angular-require-lazy實驗。 我希望它有所幫助......

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM