A Roadmap is a concept a little bit harder than a backlog to be managed using external tools. Pivotaltracker or software like that are very handy to manage a lot of things, but sometimes a roadmap’s content doesn’t fit perfectly with the approach that other software impose to you.
So while my teammates were thinking of how to aggregate the data and where to save them, I made the frontend as a Single Page Application using AngularJs and Jasmine and using Chutzpah as test runner.
One of the things I most appreciate using angular are the Directives.
The goal was to have a preview of a roadmap to use it in a dashboard able to summarize all the projects and their statuses.
What I wanted was to create a roadmap easily, something like
<roadmap-preview roadmap="roadmapContract"></roadmap-preview>
and I expected to have a result like
Having this idea in my mind, I started with some test.
I usually prefer to describe my expectations in a readable way and so I started describing a “When” situation and declaring all my expectations:
describe('', function () { describe('When the roadmap is compiled', function () { it('it should have the position of the viewer in the timeline'); it('it should have a release-detail directive'); it('it should have the timeline divided in quarter per year'); it('it should have a street'); }); });
This approach makes very easy to read all the test in Visual Studio
Because I work with templateUrl in my Directives, I have to load the template before to compile it. I tried many solutions in the past, right now I use this:
describe('', function () { var compile; var rootScope; beforeEach(module('backlogsReader.directives')); beforeEach(inject(function ($compile, $rootScope, $templateCache) { compile = $compile; rootScope = $rootScope; var roadmapPreviewTemplate = null; $.ajax({ type: 'GET', async: false, url: 'relativeToTestUrl/roadmapPreview.html', success: function (html) { roadmapPreviewTemplate = html; } }); $templateCache.put("relativeToTheApplicationRootUrl/roadmapPreview.html" , roadmapPreviewTemplate); })); ... });
I preload the template at the beginning of all the suite, so I’m sure to load it only once.
Then I compile the directive passing a mock of the data.
describe('', function () { ... describe('When the roadmap is compiled', function () { var scope; var el; beforeEach(function() { scope = rootScope.$new(); scope.stub = { whereAreYou: { month: '2', weekOfTheMonth: '4' }, releases: [{}] }; el = compile('<roadmap-preview roadmap="stub"></roadmap-preview>')(scope); scope.$digest(); }); ... }); });
I don’t follow this approach every time, it depends on the situation I want to test. In this specific case I didn’t need to test the behaviour with different kind of data, but if that was the case I couldn’t compile at once, I had to compile directly in the it() or had other beforeEach.
Speaking about the data I mocked, at that time I didn’t have a specific contract to use or an exact idea of the data we would have had, so I decided my minimum requirement to achieve my goal and I shaped it into that mock.
I try to keep things simple and avoid to make upfront design.
These tests were about roadmap visualization so I avoided to think about release details, I just assumed that I would have had a release detail and so I wrote my expectations
describe('', function () { ... describe('When the roadmap is compiled', function () { ... it('it should have the position of the viewer in the timeline' , function () { var whereAreYou = el.find('.where-are-you'); expect(whereAreYou.length).toBe(1); expect(whereAreYou.hasClass('pos-2-4')).toBeTruthy(); }); it('it should have a release-detail directive' , function() { expect(el.find('release-detail').size()).toBe(1); }); it('it should have the timeline divided in quarter per year' , function () { expect(el.find('.roadmap-q.q-1').length).toBe(1); expect(el.find('.roadmap-q.q-2').length).toBe(1); expect(el.find('.roadmap-q.q-3').length).toBe(1); expect(el.find('.roadmap-q.q-4').length).toBe(1); }); it('it should have a street' , function () { expect(el.find('.street').length).toBe(1); }); }); });
Here the complete version of my test file.
describe('', function () { var compile; var rootScope; beforeEach(module('backlogsReader.directives')); beforeEach(inject(function ($compile, $rootScope, $templateCache) { compile = $compile; rootScope = $rootScope; var roadmapPreviewTemplate = null; $.ajax({ type: 'GET', async: false, url: 'relativeToTestUrl/roadmapPreview.html', success: function (html) { roadmapPreviewTemplate = html; } }); $templateCache.put("relativeToTheApplicationRootUrl/roadmapPreview.html" , roadmapPreviewTemplate); })); describe('When the roadmap is compiled', function () { var scope; var el; beforeEach(function() { scope = rootScope.$new(); scope.stub = { whereAreYou: { month: '2', weekOfTheMonth: '4' }, releases: [{}] }; el = compile('<roadmap-preview roadmap="stub"></roadmap-preview>')(scope); scope.$digest(); }); it('it should have the position of the viewer in the timeline' , function () { var whereAreYou = el.find('.where-are-you'); expect(whereAreYou.length).toBe(1); expect(whereAreYou.hasClass('pos-2-4')).toBeTruthy(); }); it('it should have a release-detail directive' , function() { expect(el.find('release-detail').size()).toBe(1); }); it('it should have the timeline divided in quarter per year' , function () { expect(el.find('.roadmap-q.q-1').length).toBe(1); expect(el.find('.roadmap-q.q-2').length).toBe(1); expect(el.find('.roadmap-q.q-3').length).toBe(1); expect(el.find('.roadmap-q.q-4').length).toBe(1); }); it('it should have a street' , function () { expect(el.find('.street').length).toBe(1); }); }); });
Obviously all my tests failed at the first run and I had to solve them.
Pingback: Backlog Aggregator: Roadmap Feedback (part one) | Agile. Angular/Js. Asp.NET & TDD