Reputation: 10111
I have often heard developers say that test code should be 'ugly', and plain as possible.
The reason for that, being that any logic in the test, needs to be tested it self, and it creates and chicken and egg paradox.
I found myself making my test much more readable, structured and test code much more reusable by using some simple logic.
The question is, are those valid unit tests?
I am using karma as a test runner, this specific project uses a node server with connect-asset-manager, and bower packages for front end.
How can I make the code more modular(AMD? browserify?), without having to implement everything from scratch or to introduce a framework.
Currently, global state is changed across many files, and many closures, I don't like it. How can I make code within closure still testable? this package perhaps?
Example code:
utils.js
function getRealTemplatValues (inputs, templateFn, outerId, innerClass) {
var i, res;
for (i = 0; i < inputs.length; i++) {
res = templateFn(inputs[i]);
$('#' + outerId).append(res);
}
return $('.' + innerClass).map(function(){
return $(this).html();
}).get();
};
function assertEqual (done, expect, inputs, outputs, fn, decorator) {
function iter (input, output, cb) {
var i;
for (i = 0; i < inputs.length; i++) {
cb(input[i], output[i]);
}
};
function cb (input, output) {
output = !!decorator ? decorator(output) : output;
expect(fn(input, decorator)).toBe(output);
done && done();
}
iter(inputs, outputs, cb, decorator);
};
helper.scenario.js
describe('helpers', function () {
var inputs = ["#string#string#string", "string#string", "string#string#string#", "#", "###"],
outputs = ["#string #string #string", "string #string", "string #string #string#", "#", "###"],
decorString = 'magic!',
outerId = "container",
innerClass = "inner",
decorator = function(input) {return input + decorString};
describe('breakHashtags - unit', function(done) {
it('should break hashtags with prefixed with spaces - non decorated', function (done) {
return assertEqual(done, expect, inputs, outputs, breakHashtags);
});
it('should break hashtags with prefixed with spaces - decorated', function () {
return assertEqual(done, expect, inputs, outputs, breakHashtags, decorator);
});
});
describe('handle bars integration', function () {
var outerId = "container",
outerClass = "inner",
fixture = '<div id="' + outerId + '"></div>',
template = Handlebars.compile('<div class="' + outerClass + '">{{breakHashtags hashtags}}</div>'),
decoratedTemplate = Handlebars.compile('<div id="inner">{{breakHashtags hashtags decoratorHelper}}</div>');
beforeEach( function () {
addFixture(fixture);
});
afterEach( function () {
clearMyFixtures();
Handlebars.helpers['decoratorHelper'] && delete Handlebars.helpers['decoratorHelper'];
});
it('should have the breakHashtags function registered as a helper', function () {
expect(Handlebars.helpers['breakHashtags']).toEqual(breakHashtags);
});
it('should replace hashtags with hashtags prefixed with spaces', function(){
var realValues = getRealTemplatValues(inputs, template, outerId, outerClass);
assertEqual(done, expect, inputs, realValues, breakHashtags);
});
it('should replace hashtags with hashtags prefixed with ' +
'spaces and invoke the decorator on theo put', function(){
Handlebars.registerHelper('decoratorHelper', decorator);
var realValues = getRealTemplatValues(inputs, decoratedTemplate, outerId, outerClass);
assertEqual(done, expect, inputs, realValues, breakHashtags, decorator);
});
});
});
helpers.js:
function breakHashtags (text, decorator) {
var pattern = /\w(#).+/i,
p1, p2, idx = text.search(pattern), prefix = '';
while (idx > 0) {
if (idx === 1) {
text = text.substring(idx);
prefix = text.substring(0, 1);
}
else{
p1 = text.substring(0, idx + 1);
p2 = text.substring(idx + 1);
text = p1 + ' ' + p2;
console.log(p1, p2, text)
}
idx = text.search(pattern);
}
return !!decorator ? decorator(prefix + text) : prefix + text;
}
Handlebars.registerHelper('breakHashtags', breakHashtags);
Upvotes: 0
Views: 67
Reputation: 14550
my opinion: i think you went way too far. for example:
it('should replace hashtags with hashtags prefixed with spaces', function(){
var realValues = getRealTemplatValues(inputs, template, outerId, outerClass);
assertEqual(done, expect, inputs, realValues, breakHashtags);
});
it may be more readable to you but only to you. programming is a team game. when someone else sees that code first time, he has no idea what's going on. there is a description in that test but it has nothing to with what you actually test. don't know exactly what your code does but tests should look something like this:
it('should replace hashtags with hashtags prefixed with spaces', function(){
var result = testedFunction("#hashtag1#hashtag2#hashtag3");
assertThat(result).isEqualTo("#hashTag1 #hashTag2 #hashTag3");
});
Now each test is a whole on it's own. and no one has to check different files to understand it. what if someone change your helper functions? will you notice it?
How can I make the code more modular(AMD? browserify?), without having to implement everything from scratch or to introduce a framework.
what's wrong with framework? after all you've just started to write your own. it's better to use 3rd party framework because everyone in your team knows what exactly it does. when you write your own helpers, everyone has to learn them from scratch. furthermore, it's better to write helpers that makes your tests more powerful in a general way. feel free to create simple framework for parametrized testing or general purpose assertions. it's perfectly clear how it works, what it does, when and how it should be changed. so you can use them in all your tests
but when you create functions like assertEqual (done, expect, inputs, outputs, fn, decorator)
then no one knows what it does. why the hell assertEquals
has so many parameters? it should have at most 2-3 (actual, expected, error message). so can i change that assertEquals when i need? or should i leave it as is because it's important to someone and just copy-paste it? such tests are a maintenance nightmare
I have often heard developers say that test code should be 'ugly', and plain as possible.
yes, they should be plain and no, the should not be ugly. it's your code so refactor it. but refactor in a way to make them readable documentation. every single test should be a tiny part of the documentation on it's own. for everyone, not just for you. without the need of looking anything in other helper files or functions
Upvotes: 2