Sunday, August 24, 2014

Functional Programming All the Things OR How to Use Curry to Make Your Test More Readable

"I read in's looks"
-- Shakespeare, Henry VIII
Act I, Scene I, Line 125.2

We, the software craftsmen, understand that tests are a first class citizen.  If the system code was deleted it would be a great opportunity to rewrite the system and know once all the tests pass that the system has all the same functionality.  The question is should tests have the same style as the system code?

Say you have the Coin Changer kata and you want to use a function which takes both the coin value and the amount of change to be given.

If we wrote this in a C-style interface it look something like the following:

int[] changeFor(int[] coins, int amount)

While testing this we have a choice: we can either pass in the coins each time in the test thus violating the DRY principle or we could some how fill in the coins we want to use on each test in a testing series.  The second choice is very interesting since it is screaming for currying the coins per testing series.  Following this idea we have the following for the Coin Changer kata using AngularJS.



We have the following for the system code of the changeFor function.

    $scope.changeFor = function(coins, amount) {
      if (_.isEmpty(coins)) return [];

      return _.reduce(coins, function(m, coin) {
        m.change.push(Math.floor(m.amount / coin));
        m.amount %= coin;
        return m;
      }, {
        amount: amount,
        change: []
      }).change;
    };

We see that lodash's isEmpty makes sure that we have an array of coins before we go to the aggregation using the reduce function.  In the reduce we create an object with the current amount and change calculated so that we do not have to mutate the values passed in to the changeFor function.

We test the cases when we have no coins being passed in with the following code from appSpec.js.

describe('given no coins', function() {
var noCoinsFor;
beforeEach(function() {
noCoinsFor = _.curry($scope.changeFor)([]);
});
it('should return nothing for 0 cents', function() {
expect(noCoinsFor(0)).toEqual([]);
});
it('should return nothing for 99 cents', function() {
expect(noCoinsFor(99)).toEqual([]);
});
});

We set up changeFor function by currying with an empty array for the coins in the beforeEach in the top level describe.  Using curry in the beforeEach we do not have to pass in the empty array on each call in the its.  This makes the test a bit more readable.

Similarly when we test the penny series we can curry the penny.

describe('given pennies', function(){
var penniesFor;
beforeEach(function(){
penniesFor = _.curry($scope.changeFor)([1]);
});
it('should return nothing for 0 cents', function(){
expect(penniesFor(0)).toEqual([0]);
});
it('should return 1 penny for 1 cents', function(){
expect(penniesFor(1)).toEqual([1]);
});
it('should return 2 pennies for 2 cents', function(){
expect(penniesFor(2)).toEqual([2]);
});
});

This makes the grouping in the describe seem more logical and the test more readable.

The same with the nickel and penny.

describe('given nickels and pennies', function(){
var nickelsPenniesFor;
beforeEach(function(){
nickelsPenniesFor = _.curry($scope.changeFor)([5, 1]);
});
it('should return nothing for 0 cents', function(){
expect(nickelsPenniesFor(0)).toEqual([0, 0]);
});
it('should return 1 penny for 1 cents', function(){
expect(nickelsPenniesFor(1)).toEqual([0, 1]);
});
it('should return 1 nickel and 1 penny for 6 cents', function(){
expect(nickelsPenniesFor(6)).toEqual([1, 1]);
});
});

Finally our integration tests for US coins.

describe('given quarters, dimes, nickels, and pennies', function(){
var changeFor;
beforeEach(function(){
changeFor = _.curry($scope.changeFor)([25, 10, 5, 1]);
});
it('should return nothing for 0 cents', function(){
expect(changeFor(0)).toEqual([0, 0, 0, 0]);
});
it('should return 1 penny for 1 cents', function(){
expect(changeFor(1)).toEqual([0, 0, 0, 1]);
});
it('should return 3 quarters, 2 dimes, 0 nickels and 4 pennies for 99 cents', function(){
expect(changeFor(99)).toEqual([3, 2, 0, 4]);
});
});

By using the curry function we have added to the readability of the Jasmine tests.  This style is a bit different than the system code in the AngularJS application.

Check out the plunker which goes with this post.


Thursday, August 14, 2014

Continuous Learning - How to Get Your Knowledge Share On OR Notes from That Conference 2014 Open Space

"And have my learning from some true reports"
-- Shakespeare, Antony and Cleopatra
Act II, Scene II, Line 51

Here is a summary of my experience running an Open Space on Continuous Learning at my third That Conference

Ways to Share Knowledge at an Organization

  • Scheduled reports / presentation which are record 
    It is very important that they are scheduled as a recurrent event so that everyone will feel the need to take part in them.  It is also very important that they are recorded so that they can be used in on-boarding.
  • Tech Talks
    Part of being in IT is knowing about what is happening in technology.  Have your new people and interns talk about what they use to communicate with their friends, I bet they use things you have not even heard of.
  • Project Show and Tell
    Every project big or small has interesting challenges, share your solutions since they will more than likely be face again and again by others.
  • Tech and Try
    Have someone do a quick 5 to 10 minute talk on a topic and then have everyone do what was just talked about.  An example of this would be have someone present on a framework then have everyone do a code kata using that framework, you now have a bunch of people who have at least used that framework a little.

Ways to Learn More on Your Own

  • Code Kata
    Try doing the FizzBuzz kata in a bunch of different ways with a bunch of different frameworks and languages, then compare implementations.
  • Online Classes
    Pluralsight, Egghead.io, Codecademy, ... all have different ways to learn new things, find the one which works best for you.
  • User Groups
    I am a bit bias since I am a co-lead for an User Group, but there is more-likely-than-not a group of people that get together and talk about something you are interested in, join them.  If you cannot find any existing group, start one, all you need is a place to meet and other people.
  • Present at a Conference
    There is no better way to learn something than teaching others.  I know presenting and coding are two very different skill sets, but developing the ability to present and develop software will open all kinds of doors.