-- Shakespeare, Othello
Act II, Scene III, Line 338
Characterization Tests
Whenever I find code of any kind that I do not understand I like to play around with it to see how it actually works. Seldom do I find that the documentation gives me what I actual want to know, so I end up reading the source code (if I can) and playing around with some simple example code to figure out how the code I am going to use works. It was not until I read Michael Feathers' Working Effectively with Legacy Code that I learned there is an actual term for this, it is called a Characterization Test.
The idea behind a characterization test is simple. Treat the code that you are going to work with like a black box. In order to discover how the code actually works write tests to prove out the functionality. This is very similar to TDD except for one big difference, you are not going to fix the code to make the test pass. Unlike in TDD with Characterization Tests you do not own the code, so you cannot modify it.
The goal of Characterization Testing is figure out how the code actually works, not how you would like it to work.
JavaScript Function References
What does any of this have to do with JavaScript function references? Well, while reading Reginald Braithwaite's JavaScript Allongé I came across a section about how function references work in JavaScript. I had a little bit of trouble fully wrapping my head around the examples so I figured I'd do some Characterization Testing with them to fully figure out what was going on.
Using Node.js, Mocha, and expect.js I came up with the following characterization tests using a BDD style (this is my first gist on github).
Simple function examples
Given the following function:
alwaysOne = function(){return 1;};
We can show the following.
it("Given a function with two references with different names " +"they will be equal", function(){var one = alwaysOne;expect(alwaysOne).to.be.equal(one);}),it("Given a function with two references with different names " +"the results will be equal", function(){var one = alwaysOne;expect(alwaysOne()).to.be.equal(one());}),
it("Given a function with two references with different names " +"changes to the reference of one will not affect the other", function(){var one = alwaysOne;expect(alwaysOne()).to.be.equal(one());alwaysOne = void 0; // always is not forever :)expect(alwaysOne).to.not.be.equal(one);expect(alwaysOne).to.be.eql(void 0);expect(one()).to.be.equal(1);});
Next we see that if we alter where one of the references point to it leaves the other alone. Makes sense.
Given the following recursive function:
even = function(x){return x === 0 || !even(x-1);};
We can show the following.
If we have one reference to the function when we change the reference we get the new functionality referred to (in this case void 0 is returned). I know and birds go tweet, so what? This is will make more sense to check given the next test.
If we have two different references and we change the reference referred to by the recursive call we get some interesting behavior. We see that the calls to even after the reassignment conform how we would expect them to.
We see an interesting behavior on the calls to divisableByTwo after the reassignment. We see that the functionality continues to work as long as the recursive call to the altered named reference even is not made. Thus, the call using 0 will work fine since no recursive call is made, but any other call will fail since even is now referred to void 0 which is not a function.
To get around this issue you want to name the anonymous function that even is referring to.
even = function even(x){return x === 0 || !even(x-1);};
Now the recursive call made to even in the function will refer to the function named even and not the reference named even.
Recursive function examples
Given the following recursive function:
even = function(x){return x === 0 || !even(x-1);};
We can show the following.
it("Given a recursive function with one reference " +"changes to the reference will change the reference", function(){expect(even(0)).to.be.ok();expect(even(1)).to.not.be.ok();expect(even(8)).to.be.ok();even = void 0;expect(even).to.be.eql(void 0);})
If we have one reference to the function when we change the reference we get the new functionality referred to (in this case void 0 is returned). I know and birds go tweet, so what? This is will make more sense to check given the next test.
it("Given a recursive function with two reference with different name " +"changes to the reference will break it", function(){var divisableByTwo = even;expect(even(0)).to.be.equal(divisableByTwo(0));expect(even(1)).to.be.equal(divisableByTwo(1));expect(even(8)).to.be.equal(divisableByTwo(8));even = void 0;expect(even).to.be.eql(void 0);expect(divisableByTwo(0)).to.be.ok();expect(function(){return divisableByTwo(8);}).to.throwError(); // throws TypeErrorexpect(function(){return divisableByTwo(0);}).not.to.throwError(); // to prove above worksexpect(function(){return divisableByTwo(8);}).to.throwException(function(e){expect(e).to.be.a(TypeError);});expect(function(){return divisableByTwo(8);}).to.throwException(/undefined is not a function/);})
If we have two different references and we change the reference referred to by the recursive call we get some interesting behavior. We see that the calls to even after the reassignment conform how we would expect them to.
We see an interesting behavior on the calls to divisableByTwo after the reassignment. We see that the functionality continues to work as long as the recursive call to the altered named reference even is not made. Thus, the call using 0 will work fine since no recursive call is made, but any other call will fail since even is now referred to void 0 which is not a function.
To get around this issue you want to name the anonymous function that even is referring to.
even = function even(x){return x === 0 || !even(x-1);};
Now the recursive call made to even in the function will refer to the function named even and not the reference named even.