今天我已将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
});
答案 0 :(得分:4)
确定,
尝试了一下后,我能够找到解决问题的解决方案。我仍然不确定这是否是最佳解决方案,但我会在此发布我所做的事情,以便收集您的反馈。
基本上,我必须更改 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模块,我终于可以获得一致且准确的报告。
答案 1 :(得分:2)
我最近遇到了同样的问题。我在我的项目angular-require-lazy中有一个解决方案,我将对此进行描述。虽然它需要很多自定义的东西,但它最终会起作用。
首先,我做了 NOT 设法使用coverage
预处理器。由于RequireJS动态加载源,现有的预处理器不足以满足我们的需求。相反,我首先使用自定义Grunt插件手动运行伊斯坦布尔的仪器阶段:
(查看here)
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非常适合手动工作,所以如果需要,请继续使用它。
对于初学者,配置预处理器以使用预先检测的文件(注意我们将使用自定义预处理器和报告器,代码在最后):
...
preprocessors: {
'...all your sources...': 'preInstrumented'
},
preInstrumentedPreprocessor: {
basePath: '!!!SAME AS GRUNT'S dest!!!',
stripPrefix: '...the base prefix to strip, same as Grunt's cwd...'
},
...
报道记者必须考虑基线。不幸的是,原始的没有,所以我稍微调整了一下。配置是:
...
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
包含以下文件:
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;
(查看this issue了解让我“分叉”它的原因。)
// 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实验。我希望它有所帮助...