ab-testing
Version:
A/B testing made easy.
113 lines (87 loc) • 3.28 kB
JavaScript
/**
* Created by xavi on 14/12/14.
*/
var isFinite = require('lodash.isfinite'),
blueImp = require('blueimp-md5');
var md5 = blueImp.md5 || blueImp; // Forces this to work for webpack too: https://github.com/auth0/lock/pull/459/files
/**
* The config object contains a the information of the different tests, their names and weights.
*
* @type {Function}
*/
module.exports = {
createTest: function (name, config) {
if(typeof name !== 'string') {
throw Error('Insert a valid name for the test');
}
var testData = initTest(config);
var testName = name;
var getName = function () {
return name;
};
var getGroup = function (user) {
if(user === undefined || typeof user !== 'string')
throw Error("Undefined user to give it a random group");
var hexMD5hash = md5(testName, user).substr(0,8);
var hashAsInt = parseInt("0x" + hexMD5hash, 16);
var maxInt = parseInt("0xffffffff", 16);
var random = hashAsInt / maxInt;
var filtered = testData.filter(function (t) {
return t.weight > random;
});
if(filtered.length === 0)
throw Error('Error filtering the tests');
return filtered[0].name;
};
var test = function (testGroupName, functions, that) {
if(typeof testGroupName !== 'string')
throw Error('Test Group is not a string');
if(!Array.isArray(functions))
throw Error('Introduce an array of functions as a second parameter');
if(functions.length !== testData.length)
throw Error('The number of functions does not match the number of test groups');
var pluckedValues = testData.map(function (data) {
return data.name;
});
var index = pluckedValues.indexOf(testGroupName);
if(index === -1)
throw Error('Test Group not found');
if(that === undefined)
that = this;
functions[index].apply(that, functions[index].arguments);
};
return {
getName: getName,
getGroup: getGroup,
test: test
}
}
}
var initTest = function (config) {
var defaultWeight = 0.5;
var accumulativeWeight = 0;
if(!Array.isArray(config))
throw Error("Module not configured. Check your configuration.");
// If no weight is provided, we give a static weight
var totalWeight = config.map(function (o) {
return isFinite(o.weight) ? o.weight : defaultWeight;
}).reduce(function (a, b) {
return a + b
});
var tests = config.map(function (test) {
if(test.name && typeof test.name === 'string') {
return {
// Weight from 0..1
weight: (isFinite(test.weight) ? test.weight : defaultWeight) / totalWeight,
name: test.name
}
}
}).sort(function (a, b) {
return b.weight - a.weight
});
tests.forEach(function (o) {
o.weight += accumulativeWeight
accumulativeWeight = o.weight
});
return tests;
};