Testing behaviors - BDD with Mocha

We really will not do this BDD-style because we have very little space left in the book and writing about automated testing to any significant degree can fill way more pages than we really have left. We will update our typescriptSankey function to filter by a particular relationship when the user clicks on a node; however, do so in a BDD fashion, which should hopefully give you a taste of how all this works. Just remember that testing is generally best when you write your tests before you write any other code!

To start, create a new file in lib/chapter9, called index.spec.js. The convention is to name your test file the same as the file it's testing, but with .spec before the file extension. Before we write any code, it's often helpful to write out our goals in plain English:

  • The chart should reduce the opacity of irrelevant segments when a node is clicked on
  • The relevant segments should remain at full opacity
  • Clicking on a node again should return opacity to full for all parts of the chart

As you'll see, our assertions in Mocha will resemble these statements quite closely. Open up index.spec.js and add the following:

import * as d3 from 'd3'; 
import chai from 'chai';
import sankey from './index.ts';

chai.should();

describe('functional tests for UK election sankey', () => {
describe('select() method', () => {
it('should set change opacity when supplied an argument',
async () => {
const chart = await sankey();
});
});
});

First, we import our chart library and our data and set up our parent describe block. The describe tag is used to organize your tests. Having one parent describe block per .spec file that then contains more logical groupings is a good convention to follow. We then scaffold our assertions using it, similar to describeit groups the assertions we'll soon write in each it statement's callback function. Think of each it statement as a test, and the assertions contained therein as the parts of the test needed to verify what the test implies.

We will exercise each part of the chart to ensure that it returns the right data. We'll create a helper function first to save us some typing. Put this inside the it block:

      function getOpacities(source = null) { 
chart.select(source);

const links = d3.selectAll('.link[opacity="1"]')
.data()
.map(d => d.target.name)
.sort();
const nodes = d3.selectAll('.node[opacity="1"]')
.data()
.map(d => d.name)
.sort();

return {
links,
nodes,
};
}

It's time to write our tests. As our chart is an async function, we'll use a few more async functions to test each part of what we're doing:

After getOpacities(), add the following:

      await (async () => { 
const { links, nodes } = getOpacities('CON2010');
links.should.eql(['CON2015', 'LAB2015', 'UKIP2015']);
nodes.should.eql(['CON2010', 'CON2015', 'Green2015',
'LAB2015', 'LIB2015', 'Other2015', 'UKIP2015']);
})();

This returns two arrays from getOpacities(), which we compare against the expected results. Note that we sorted our values in getOpacities(), so our expected values should reflect that.

Here are the rest of the tests; put them directly after the last one:

      await (async () => { 
const { links, nodes } = getOpacities('LAB2010');

links.should.eql(['CON2015', 'LAB2015', 'Other2015']);
nodes.should.eql(['CON2015', 'Green2015', 'LAB2010',
'LAB2015', 'LIB2015', 'Other2015', 'UKIP2015']);
})();

await (async () => {
const { links, nodes } = getOpacities('LIB2010');

links.should.eql(['CON2015', 'LAB2015', 'LIB2015',
'Other2015']);
nodes.should.eql(['CON2015', 'Green2015', 'LAB2015',
'LIB2010', 'LIB2015', 'Other2015', 'UKIP2015']);
})();

await (async () => {
const { links, nodes } = getOpacities('Green2010');

links.should.eql(['Green2015']);
nodes.should.eql(['CON2015', 'Green2010', 'Green2015',
'LAB2015', 'LIB2015', 'Other2015', 'UKIP2015']);
})();

await (async () => {
const { links, nodes } = getOpacities('Other2010');

links.should.eql(['LIB2015', 'Other2015']);
nodes.should.eql(['CON2015', 'Green2015', 'LAB2015',
'LIB2015', 'Other2010', 'Other2015', 'UKIP2015']);
})();

It's pretty similar all around. Let's add a final test to exercise what happens when we deselect everything:

      await (async () => { 
const { links, nodes } = getOpacities(null);
links.should.have.length(13);
nodes.should.have.length(11);
})();

Here, we're being a bit lazy and just checking the length of the result array. We can check the actual versus expected values again if we wanted, though.

Now that we've built our tests, it's time to make our code pass them! Start up our server by running the following:

$ npm run start-server

You'll see a message about our tests failing, like this:

This is actually a good thing; it shows that we will not get false positives when our tests actually start passing. False positives are a hazard of testing; if you don't do an asynchronous test properly, it will still pass. Luckily, we're using async/await here, which abstracts away a lot of the problematic bits. Normally, you pass a function as the first argument in your it() declarations called done(), which you then call once your async operation and assertions are complete. Still, always ensure that your tests fail properly while you're writing them!

Another upside to these tests failing is that it tells us where our tests are failing, which we can use to quickly debug problems with our code. This is the whole point of tests--to be able to pinpoint exactly where something's broken when something goes wrong during development.

Let's fill in the rest of our chart. Add the following to the bottom of typescriptSankey() in index.ts:

  const select = (item: string|null) => { 
if (item) {
const filteredLinks = sankeyData.links.filter(d =>
d.source.name === item);
const filteredNodes = sankeyData.nodes
.filter(d => d.name === item || d.name.match(/2015$/));

svg.selectAll('.link')
.attr('opacity', d => d.source.name === item ? 1 : .3);
svg.selectAll('.node')
.attr('opacity', d => (d.name === item ||
d.name.match('2015')) ? 1 : .3);
} else {
svg.selectAll('.link')
.attr('opacity', 1);

svg.selectAll('.node')
.attr('opacity', 1);
}
}

Now, we add our event handlers:

  let current = null; 
node.on('click', e => {
if (current === e.name) {
current = null;
} else {
current = e.name;
}

select(current);
});

Finally, return our methods to resolve the async function promise:

  return { 
select,
node,
link,
data: sankeyData,
};

Click on Save and we're good to go!:

Ah, rats! We still have a failing test. It's due to that superfluous Ukip2010 node we had earlier; I kind of messed up when creating the dataset and couldn't figure out how to remove it. Let's make it work, though!

Add this bit before our select() method in index.ts:

node.selectAll('rect[height="0"]') 
.each(function(d){
this.parentNode.remove();
});

This is kind of a hack, but let's work with it. We select all the rectangle nodes without any height (such as our "UKIP2010" node), and remove the parent, which effectively gets rid of the misplaced "UKIP 2010" label. Hit Save:

All of our tests are now passing and we are now Tony Doneza!

Once again, there's absolutely no way I can go into a topic this dense in the space of one chapter, but hopefully you get a sense of how you can start building testing into your projects. If this is something that interests you, I highly recommend doing quite a bit of more reading about the philosophy behind automated testing and BDD; although writing tests is fairly easy, writing good tests takes quite a lot of skill to master. Even though automated testing is a good tool for giving yourself confidence in your data visualizations, don't trust it absolutely. It's possible to write a whole lot of tests that don't really test anything, and things such as confirmation bias have a tendency to creep into test writing. That aside, it can be a tremendously helpful tool for producing world-class code.

Next time you're feeling like you're an awesome automated test writer, do this fun 2-minute interactive quiz from the NYT and then, if you get it wrong, write more negative test assertions. You can find the quiz at http://www.nytimes.com/interactive/2015/07/03/upshot/a-quick-puzzle-to-test-your-problem-solving.html.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset