Sunday 23 August 2015

JavaScript: running Jasmine unit tests from the CLI

G'day:
I can't see as this will be very long as I've not much to say about it, and it was surprisingly quick to sort out once I bothered to do so.

My weekend mission was going to be to start getting up to speed with Clojure, by reading & working through the relevant chapters from Seven Languages in Seven Weeks, but whilst that kept me occupied on my flight from London to Shannon, I didn't really revisit it after that, as I got sidetracked on these Jasmine tests. Well firstly I decided my mission would be to do a Clojure version of my "get a subseries" quiz ("Something for the weekend? A wee code quiz (in CFML, PHP, anything really...)"), and before doing that I wanted to get a working version of the code I discussed in "Some CFML code that doesn't work" working, chosing JavaScript as my control language. And in deciding to do that, I decided not to just copy and paste it into the browser console to test it, I decided to do it properly and do a file-system-based exercise running it via Node (note to Acker: I already use Node. Just sparingly because the need for it doesn't crop up for me that often. You don't actually have any special knowledge the rest of us also don't already have). And if I was gonna do that, then I was also gonna get Jasmine working via Node too, as we actually do have a real requirement for this at work. We have an ever increasing amount of ever increasingly complex JS in our application... and so far not a line of testing gets done on it. We're shifting our mindset to be writing more testable JS: reducing inline callbacks; putting a much of our code in "class" file, and writing small, clean, testable methods an the like all ready for testing... but getting the actual test infrastructure up and running is just not happening.

So, anyway... other than installing Clojure via Leiningen and runing "G'day World" via the REPL, my Clojure investigations didn't move much.

But  I got the JavaScript version of my CFML code running, and also got its tests running via the commandline too. So that's cool.



As a refresher, this is the CFML I am basing this on:

function getSubseries(series, threshold){
    return series.map(function(element, index, series){
        return series.slice(index,(series.len()-index)+1).reduce(function(best,current){
            return best
                .update("running", best.running && best.run.sum() + current <= threshold)
                .update("run", best.running ? best.run.append(current) : best.run);
        },{run=[], running=true});
    }).sort(function(e1,e2){
        return sgn(e2.run.len() - e1.run.len()) ? sgn(e2.run.len() - e1.run.len()) : sgn(e2.run.sum() - e1.run.sum());
    })[1].run;
}

This is quite pleasing, except for the fact it doesn't work. See the article linked-to above for details: both ColdFusion and Lucee have (different ~) bugs preventing it from actually running.

The JavaScript version of this algorithm I came up with is not quite as tidy unfortunately, but is about as good as my JavaScript skills will work for me:

// Series.js

var Series = function(value) {
    this.value = value;
};

Series.prototype.getSubseries = function(threshold){
    var workingSeries = this.value.slice();
    var sorted = this.value.map(function(element, index, series){

        var thisSubseries = workingSeries.reduce(function(best, current){
            var potential = best.run.slice();
            potential.push(current);
            best.running = best.running && Series.sum(potential) <= threshold;
            best.run = best.running ? potential : best.run;
            return best;
        },{run:[], running:true});

        workingSeries.shift();

        return thisSubseries;
    }).sort(function(e1,e2){
        return (e2.run.length - e1.run.length) || (Series.sum(e2.run) - Series.sum(e1.run));
    });
    return sorted.length ? sorted.shift().run : [];
};

Series.sum = function(array){
    return array.reduce(function(sum,addend){return sum+addend;},0);
};

module.exports = Series;


I need a few extra intermediary values in the compared to the CFML version, plus my own sum() method to sum an array. The CFML version is nicer. Except for not working ;-)

Oh, and note the one nod to Node in there: I'm exporting Series for use in other files.

Anyway, the code itself is not the issue here.

The tests are also very much the same as code I've published before, so I won't bore you too much with it. It's on Github though: seriesSpec.js. The key bit that's different here is this:

describe("Subseries", function(){
    var Series = require("./Series.js");

    describe("TDD tests", function(){

        it("returns an array", function(){
            var series = new Series([]);
            var result = series.getSubseries(0);

            expect(result).toEqual([]);
        });
        // etc

That just imports the Series "class" into my test file for use. Thereafter I can just go new Series() as one normally would with JavaScript.

I was surprised to see I had never got around to installing Node on this machine (it's on all my other machines), but getting it up and running is just downloading the installer (I'm on Windows), and run it. This sticks the node executable on the path and what have you, and it can be run just like that. I have a testSeries.js file which does a quick manual test which one can run directly from node:

// testSeries.js

var seriesToTest = eval(process.argv[2]) || []; // eg: [100,300,100,50,50,50,50,50,500,200,100]
var thresholdToTest = process.argv[3] || 0 

var Series = require("./Series.js");
var series = new Series(seriesToTest);
var subSeries = series.getSubseries(thresholdToTest);
console.log(subSeries);

This can be run from the DOS prompt:

C:\src>node testSeries.js [100,300,100,50,50,50,50,50,500,200,100] 500
[ 100, 50, 50, 50, 50, 50 ]

C:\src>

That's all well and good, but I need Jasmine running. Hitting the first match on Google for "node jasmine" (Using Jasmine with node) has the instructions are right there, occupying the whole right-hand side of the page:

C:\src>npm install -g jasmine

I did this yesterday so don't have the output to also show you, but the bottom line is that NPM downloaded and installed it. Cool.

Now I read the docs a bit. By default Jasmine is expecting a project to be defined and laid out in a certain way, which I could not be arsed doing. However it can also be told just to do what one wants, by pointing it at a json config file. In the directory I have Series.js and seriesSpec.js, I just created this jasmine.json file:

{
    "spec_dir": ".",
    "spec_files": [
        "seriesSpec.js"
    ]
}

This says that the test spec files are in the current directory, and the one I want to run is seriesSpec.js. Now I just tell Jasmine to use that file rather than looking in its normal location, and away I go:

C:\src>jasmine JASMINE_CONFIG_PATH=jasmine.json
Started
...............


15 specs, 0 failures
Finished in 0.012 seconds

C:\src>

(in reality it took a fair bit of tweaking to get zero failures, but that was all down to my lack of JavaScript knowledge with how some of the array methods behave. Nothing interesting).

I still need to look at how Jasmine wants itself deployed / configured to run within a project environment, and also need to see what's involved in getting our CI server to run the tests, but this is a good starting point.

So that was a pub evening well spent.

Righto.


--
Adam