The Test

Previously

This article continues my effort to explore the notion that one can develop software without being an actual developer as long as they are good at Googling.

That, of course, is a paraphrase of the question posed to Scott Hanselman who wrote an article in response. Since I’m curious about this idea, I’ve decided to document my Googling while developing an approach to one of the kata's mentioned in the Hanselman article.

Test First

I’ve yet to reach the point where I’m truly doing test-driven development. I often use the term “test-centered” when talking about my approach. You’ll notice in the first draft of my kata solution that there isn’t a test, i.e. I didn’t follow the instruction to write the unit test first. As penance and to fully embrace iterative software development, I am refactoring my code and creating a test to verify its functionality.

The first thing I did was research unit testing in Node.js since that’s my environment. This led me to a nice step-by-step on the codementor site for unit testing with Mocha and Chai. I’ve added a TODO item to compare other testing frameworks but, for now, the Starbucks stuff suffices.

Here’s my implementation of the kata’s test:

var chai = require('chai');
var t = {
    expect : chai.expect, 
    testDeps : function(){
        describe('Dependencies', function(){
           it('Test dependency graph', function(){
               var deps = require('./../src/transdep');
               deps.addDependency('A',['B','C']); 
               deps.addDependency('B',['C','E']); 
               deps.addDependency('C',['G']); 
               deps.addDependency('D',['A','F']); 
               deps.addDependency('E',['F']); 
               deps.addDependency('F',['H']); 
                                  
               t.expect(deps.getDependencies('A')).to
                .deep.equal(['B','C','E','G','F','H']);
               t.expect(deps.getDependencies('B')).to
                .deep.equal(['C','E','G','F','H']);
               t.expect(deps.getDependencies('C')).to
                .deep.equal(['G']);
               t.expect(deps.getDependencies('D')).to
                .deep.equal(['A','F','B','C','E','G','H']);
               t.expect(deps.getDependencies('E')).to
                .deep.equal(['F','H']);
               t.expect(deps.getDependencies('F')).to
                .deep.equal(['H']);
            });
        });
    }
}
t.testDeps();

Refactor

Attentive readers will notice my deviation from the kata. Specifically, the kata test expects a sorted dependency list. I’ll tackle that requirement in my next article. For now, let’s pretend I’ve convinced QA that the dependency order doesn’t matter. The order is deterministic so… On to the refactored code.

The test expects the dependency graph to have a way to add dependencies and to retrieve dependencies (direct and indirect) for a given object. The original code has this functionality living in the readDataFromCli and graphDependencies functions (highlighted below).

readDataFromCli: function(){
    process.stdin.resume();
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', function(line) {
        var data;
        var i = 1;
        if (line === 'q\n') { 
           app.graphDependencies(); 
        } else {
            data =  app.processLine(line);
            app.deps[data[0]] = [];
            for(; i < data.length; ++i){
                app.deps[data[0]].push(data[i]);
            }
        }  
    });
}
graphDependencies: function (){
    console.log('Original Chart:');
    console.log(app.deps);
    for(var aKey in app.deps){
        for(var bKey in app.deps ){
            if(aKey !== bKey && 
                app.deps[aKey].includes(bKey)){
                app.deps[aKey] = app.concatDeps
                                (aKey,
                                 app.deps[aKey],
                                 app.deps[bKey]);
            }
        }
    }
  
    console.log('Dependency Graph:');
    console.log(app.deps);
    process.exit();
}

Moving the highlighted code into their own functions looks like this:

   addDependency : function(name, dArray){
        if(!deps[name]) deps[name] = [];
        deps[name] = util.concatDeps
                    (name,
                     deps[name],
                     dArray);
    }

    getDependencies : function(name) {
        var myDeps = []
        if(deps[name]){
            myDeps = deps[name].slice();
            for(var dKey in deps){
                if(myDeps.includes(dKey)){
                    myDeps = util.concatDeps
                            (name,
                             myDeps,
                             deps[dKey]);
                }
            }            
        }
        return myDeps;
    }

While refactoring the “add dependency” behavior I introduced support for calling addDependency() multiple times for an individual object. This provides a way to build up dependencies dynamically instead of defining them all at once.

I also split two of the “helper” functions into their own object instanced in the util variable. I did this for two reasons:

First, introducing client code (i.e. the test) made a public interface1 necessary and I want as lean and clean an interface as possible.

Second, it made sense to move generic functionality out of the dependency graph object. Plus, both the concatDeps() and the processLine() functions have high reusability potential.

Initially, I went to remove the concatDeps() function and use a Set to hold the dependencies. Thus removing the need for the manual array concatenation. However, some known issues with chai hinder that approach. I’ll keep an eye on their forum as this project progresses.

Here is the fully refactored code:

/* Since module.exports is needed for testing
 * Only include "public" functions in the exported object
 */
var deps = {};
var util = {
    concatDeps : function(key,a,b) {
        var arr = [];
        var i = 0;
        if(a.length === 0){
            arr = b.slice(0);        
        } else {
            for(; i < a.length; ++i){ arr.push(a[i]);}
            for(i = 0; i < b.length; ++i) {
                if(key !== b[i] && 
                  !arr.includes(b[i])){ 
                   arr.push(b[i]); 
                }
            }    
        }
        return arr;
    },    
    processLine : function (line){
        var depData = line.substring(0,line.length - 1);
        return depData.split(' ');
    }
};

/* This becomes the API */
module.exports = dGraph = {
    displayGraph : function (){
        var gDeps = {};
        console.log('Original Chart:');
        console.log(deps);

        for(var aKey in deps){
            gDeps[aKey] = 
             dGraph.getDependencies(aKey);
        }
  
        console.log('Dependency Graph:');
        console.log(gDeps);
    },
    addDependency : function(name, dArray){
        if(!deps[name]) deps[name] = [];
        deps[name] = util.concatDeps
                    (name,
                     deps[name],
                     dArray);
    },
    getDependencies : function(name) {
        var myDeps = []
        if(deps[name]){
            myDeps = deps[name].slice();
            for(var dKey in deps){
                if(myDeps.includes(dKey)){
                    myDeps = util.concatDeps
                            (name,
                             myDeps,
                             deps[dKey]);
                }
            }            
        }
        return myDeps;
    },
    readDataFromCli : function(){
        process.stdin.resume();
        process.stdin.setEncoding('utf8');
        process.stdin.on('data', function(line) {
            var data;
            var i = 1;
            if (line === 'q\n') {  
                dGraph.displayGraph(); 
                process.exit();
            } else {
                data =  util.processLine(line);
                dGraph.addDependency(data[0],data.slice(1))
            }  
        });
    }
};

Verify

The advice to write tests first cannot be overstated. As I said, I’m not there yet and doing this kata has reinforced my resolve to try harder. It’s certainly not easy. However, restructuring my code for the test raised questions I didn’t have to think about when creating the functionality in a silo, i.e. writing for a single use case. Answering those questions forced robustness and that is reason enough to start with the test first.

Throughout most of my career well-defined requirements have been elusive. Specifications are often amorphous at the beginning and sometimes, truth be told, written to match what’s been coded after the fact. Thinking about the unit tests during requirements gathering offers a focal point for identifying and solidifying specifications. When I’ve achieved the test being the specification I’ll buy myself a drink.

--
Randy


  1. I’m using this term generically to mean visible to other files. The approach I’m using comes from information I gathered here and here

Approaching Iterativity

Initialization

I recently came across an article in which Scott Hanselman responded to a reader who wanted to know how to determine if they were actually a developer or just a good Googler.

Until I read this I hadn’t even thought about it. Not in those terms anyway. I think of myself as a successful developer, i.e. I’ve been gainfully employed as a developer for years. However, I don’t know if I’m actually a developer. I’ve certainly met and read the code of much better developers, people I consider actual developers, so the concept interests me. I’m certainly not a good Googler (mediocre at best) which means I should strive to determine if I’m actually a developer.

To that end, I’ve decided to take the advice Mr. Hanselman kindly offered his reader and work on one of the Code Katas while documenting the reference material I use. Since I’m currently polishing my proficiency1 with JavaScript à la Node.js that’ll be the software context.

Condition

The kata I’ve chosen is transitive dependencies and, knowing that I want to do data entry via the command line, I hit up Google to see what I can get from Node.js. Stackoverflow, the result for most of my Googling, taught me2 about the process object as well as some npm modules .

Since I’m just doing a kata, not developing a general, all-purpose solution, I opted to adopt the technique illustrated by this article in the nodejitsu documentation. As you’ll see in the code, I went with the complicated “simple example”.

I should mention that I tend (i.e. rarely fail) to reference the language documentation about everything. For JavaScript I use the MDN. I did go to stackoverflow for some of the object property stuff. Specifically, this question and this question.

Increment

Here’s my first attempt with the code kata. I don’t like the nested loops and I’m sure my JavaScript is sloppy so I’ll be coming back to make some improvements in a later article.


var app = {
   deps: {},
   concatDeps: function(key,a,b) {
       var arr = [];
       var i = 0;
       for(; i < a.length; ++i) { arr.push(a[i]); }
       for(i = 0; i < b.length; ++i) {
           if(key !== b[i] && 
              !arr.includes(b[i])) { 
                  arr.push(b[i]); 
           }
       }
       return arr;
   },
   processLine: function (line) {
      var depData = line.substring(0,line.length - 1);
      return depData.split(' ');
   },
   graphDependencies: function () {
      console.log('Original Chart:');
      console.log(app.deps);
      for(var aKey in app.deps) {
         for(var bKey in app.deps ) {
            if(aKey !== bKey && 
               app.deps[aKey].includes(bKey)) {
               app.deps[aKey] = app.concatDeps
                               ( aKey,
                                 app.deps[aKey],
                                 app.deps[bKey] );
            }
         }
      }
      console.log('Dependency Graph:');
      console.log(app.deps);
      process.exit();
   },
   readDataFromCli: function() {
      process.stdin.resume();
      process.stdin.setEncoding('utf8');
      process.stdin.on('data', function(line) {
         var data;
         var i = 1;
         if (line === 'q\n') {  
            app.graphDependencies(); 
         } else {
            data =  app.processLine(line);
            app.deps[data[0]] = [];
            for(; i < data.length; ++i) {
               app.deps[data[0]].push(data[i]);
            }
         }  
      });
   }
};

app.readDataFromCli();

Statement

What I learned by both doing the code kata and tracking my resources is that my reference material didn’t tell me how to accomplish the task. I knew what I needed to look up by understanding the coding problem and my approach to its solution.

Maybe I misunderstood the initial question posed to Scott Hanselman. Was I supposed to use Google to find a solution? That option didn’t occur to me until just now. I’m going to have to work on my reading comprehension, seriously.

--
Randy


  1. I’ve written JavaScript (poorly) since the 1990s. I’ve just never had much opportunity to really dig deep since I don’t use it professionally
  2. This question launched the deep dive that ultimately led to the nodejitsu article. 
Aside

Hello world!

I might as well start this off with a nod to tradition by posting (what I recall to be) my first software program.

#include <stdio.h>
int main(void)
{
    printf("Hello World!\n");
    return 0;
}