My friend and colleague, Dan Martinez, recently pointed me to Ian Cooper’s NDC talk, TDD where did it all go wrong. This is definitely recommended viewing for practical and “back to basics” TDD for neophytes and people who think they’re experts alike.
For me, I took away some new concepts, but it also clearly validated a lot of the practices that have emerged in my work and that I’ve picked up from my colleagues.
A particular point which is worth repeating is that TDD is BDD. Good tests test behavior, and behavior is expressed through public interfaces.
Restraining tests to interfaces doesn’t lead to an underpowered test suite, because all internal code should exist in the service of some behavior. Therefore testing the public interface for that behavior will provide complete coverage; any uncovered code implies unintended behavior.
This point is easily forgotten and probably often dismissed by recent converts as lazy or impure. However it is powerful, because it leads to focused development, lean suites and less rigid tests.
Applied to JavaScript Modules
Adding to this idea, it has implications for my current world of JavaScript in an unexpected way.
Modules, whether following the simple IIFE pattern or a CommonJS standard, export all functionality in an inherently public fashion. That implies, for the purposes of TDD, that a module is a behavior-level component. Which means that non-behavioral components, including support classes, must be completely scoped within and hidden by a module.
This is a somewhat hard pill to swallow for me, because, as a long-standing style practice (that I had ingrained in me from StyleCop in C#), I follow a “one class/one file” rule. But I’m trying to look at it as permission to break the rules and get that edge back into my coding!
It’s easy to declare a technique to be correct, but it’s another thing to try and make it practical.
So after writing about Oreo Testing, I decided to convert the getJqueryMockDocument function from my sample into something that could be distributed and used by others. Given its nature, a jQuery plugin seemed like the most natural option.
It should surprise no one who's read this far that I rewrote it using TDD to ensure that it:
follows jQuery’s basic and advanced guidelines for plugin creation
performs its expected functions across all common, current browsers (of course)
Usage: Updating the Façade Example
Its usage should be straight-forward enough: invoke the plugin to separate a selected (or created) node from the current DOM and isolate it into a new one.
Using our earlier façade example as a demonstration, it would be changed by:
Replacement lines for calls to getJqueryMockDocument using rescope
In my opinion, not only is this obviously much easier to incorporate into other projects, this is much more concise and readable.
Set it Free(ly licensed on GitHub)!
The last step in distributing software is making it available! I have done so, not through some run-of-the-mill lazy link on my blog! Oh no!
I tried something new (and long overdue) and created a repository for jquery-rescope on GitHub. I cordially invite you to browse the code, download it, submit pull requests, write up issues and otherwise make me feel like a real open sourcerer!
More to Come?
For all the pixels I've spilled on this topic, I doubt this will be the last time that I write about it.
For example I stumbled into the interesting practice of wrapping each test modules in an IIFE so that I could run them all from a single HTML page without corrupting each test's namespace.
Most importantly, I haven’t found or heard any feedback that this combination of black-box and white-box testing is widely known or practiced. Given all of its advantages, I can imagine myself continuing to advocate it as the Right Way to do TDD from now on.
In my post jQuery in TDD is Serious Business (Don't Mock It), I argued that jQuery (and any DOM manipulation) should be isolated in a view façade to avoid mocking its behavior and creating brittle tests. This is fine advice for creating a resilient view layer, but it doesn't address how to avoid the same problem when the view façade needs to be tested.
White-Box Testing
While writing our view, we had an implicit goal to isolate it from everything external to it, including other units (i.e. using mocks) and third parties (e.g. encapsulating jQuery into a view façade). Besides ascending to a higher plane of code modularity, this isolation serves two critical purposes:
allowing a tester to quickly identify the exact unit and test that is failing when a failure occurs without having to walk down the application stack. In other words, a failure in testing equals a failure in exactly one unit
freeing us to start and finish any unit of work without having to wait for a depended-upon unit to be written
In my experience, this leads to Test-Driven Development (TDD) practically assuming a white-box testing approach to development.
If we try and apply that wisdom that to our view façade however, we encounter all of the same problems we had been trying to avoid. At the very edge of our stack, with nowhere left to push jQuery, we have to face the music. We have to either mock jQuery or find another way.
Black-Box Testing
One of the major objections I raised to mocking jQuery, was how it turns virtually any code change into a test failure even if the unit's behavior hasn't changed. If we rephrase that positively as “we want to write tests that pass, regardless of the implementation, so long as they produce the correct results,” we stumble upon black-box testing.
In black-box testing we prepare a test environment with known data, perform the tested action and then verify success against expected output.
In our example, this means creating a free-standing DOM for testing purposes, contextualizing a jQuery instance to it and passing that as the dependency to our view façade.
Example
Without going through the individual steps this time, let's set up a new test environment with QUnit and include jQuery itself on the test page this time.
As a refresher, the interface we defined for our view façade is:
facade = {
getCheckbox: function () {
},
bindClick: function (element, callback) {
},
getFieldset: function () {
},
toggleElement: function (element) {
}
};
I apologize for the horrible naming scheme. It can be so hard to make up with a decent example.
Let's dig in and write a quick outline for a test against the getCheckbox method:
test('getCheckbox returns the correct checkbox', function () {
'use strict';
var checkbox,
foundCheckbox;
unit = new SampleViewFacade($);
foundCheckbox = unit.getCheckbox();
equal(foundCheckbox, checkbox, '');
});
Test that instantiates the unit, calls the getCheckbox method and an assert comparing the returned value and a test value.
If we were writing a white-box test, our next step would be to mock jQuery to return checkbox as a test value for the expected call chain. Instead we need to construct a test environment with a DOM supplied by the and a jQuery instance that interacts with that environment.
Contextualizing jQuery to a Mock DOM
To use jQuery with predictable data, I've written the following function to inject arbitrary HTML into the body element of an iframe and return a jQuery object confined to its context.
function getJqueryMockDocument (html) {
var $ref, $doc;
$('iframe#jqueryMockDocument').remove();
$ref = $('<iframe id="jqueryMockDocument" style="display: none;">').appendTo('body').contents();
$doc = $ref.extend(function (selector) { return $ref.find(selector); }, $ref);
// For IE (head missing immediately on iframe add)
if ($doc('head').length === 0) {
$doc[0].write('<head>');
}
// For IE (body missing immediately on iframe add)
if ($doc('body').length === 0) {
$doc[0].write('<body>');
}
if (html) {
$doc('body').append(html);
}
return $doc;
}
This function creates (or replaces) an IFrame on the page to allow access to elements and nodes outside of the body, such as head and document.
It then selects the contents of the body within that IFrame and extends it with a wrapper of jQuery's find method. This is to complete the mock to allow $ selector calls.
The final part appends the html parameter to the body of the new IFrame.
The remainder is dealing with the fact that IE was confused, got up from the kid's table and wandered over to where adult browsers were talking.
Writing the Mock HTML
Now that we can convert an HTML fragment into a jQuery instance running against it as a DOM, let's put it to use!
Since our application is only as large as my contrived example, it's reasonable to assume that the "correct" checkbox will be the first checkbox within our application's namespace. Our HTML will therefore be an outer div with a class for our namespace, sample, and a checkbox we expect to have returned. For the sake of testing, we'll set an attribute on the checkbox to make confirming we've selected the correct checkbox easier.
Our updated variable declaration is now:
var foundCheckbox,
$mock = getJqueryMockDocument('<div class="sample"><input id="correctElement" type="checkbox" />');
Our updated assert now is:
equal($(foundCheckbox).attr('id'), 'correctElement', 'Found the checkbox with the expected ID.');
Finally, with our contextualized jQuery we need to update our test statement to:
unit = new SampleViewFacade($mock);
Following the TDD process, we'll write code to make the test pass by
defining the SampleViewFacade class
adding a getCheckbox method
accepting jQuery as a parameter in the constructor
selecting and returning the checkbox element
which looks like:
function SampleViewFacade ($) {
var me = this;
me.getCheckbox = function () {
var result = $('.sample :checkbox:first').eq(0);
if (result.length === 1) {
return result[0];
}
return null;
};
}
Black vs. White Box Testing
This example demonstrates that we can write a test that accurately validates success without mocking jQuery. If this is advantageous, and works for our view façade, then what’s the catch? Why didn't we test the view this way?
Developing our view in TDD without any opinion of its internals would have meant:
being unable to use internal-aware mock dependencies
needing to wait for the view façade to be written
speculating on the view's requirements when defining the view façade interface
tests failing both if the view failed or if the view failed because the view façade failed
Since we have a workable white-box testing regime for developing our views, this isn't such a hard a pill to swallow. In terms of our view façade, the first three are similarly easy to accept, because they aren't problems in this case, but how do we handle the issue of isolating the view façade as a unit for testing?
Since the only dependency is jQuery and we have to operate under the assumption that it functions correctly and according to their documentation, there is no ambiguity when a test fails: it fails within our unit. Therefore black-box testing has none of these shortcomings for the view façade, because the view façade is already isolated.
Oreo Testing
This example has focused on jQuery, but it applies to any third-party integration -- which implies an overall approach for TDD from end-to-end.
At either end of the application stack, where units interact with the host environment, black-box testing can be employed without losing any desirable aspects of white-box testing and should be employed to minimize test brittleness.
Within the interior of an application, where units only interact with other application units, white-box testing should be employed to isolate units, define interfaces and decrease wait time in development.
As described in my later post, My "View Adapter" Was Just a Façade, I updated this post to refer to a "view façade" instead of a "view adapter" as it had originally been written.
Test-driven development in JavaScript is easy to the point of inducing maniacal laughter -- my office mates don't seem to appreciate this -- because its loosely-typed nature allows you to create mock implementations in-line with writing your tests.
Separately, jQuery brings joy to working with JavaScript in the browser by providing access to the DOM in a way that is natural and consistent.
Unfortunately, these twin marvels don't automatically become one euphoric JavaScript mega-experience when brought together. Instead, I've found a sort cultural impasse between TDD and jQuery. TDD forces you to think "what steps will I take to solve a problem," and jQuery tend to make you think "jQuery solves ALL THE THINGS!" The result is a collection of tests which are highly opinionated about how jQuery is used, instead of being solely opinionated about whether or not the problem is solved.
Let's take a simple example built-out using TDD to demonstrate what I mean by tests that are opinionated about jQuery's implementation and how this most often happens. If code examples bore you, feel free to skip past this one and go right to my conclusions.
Example: Mocking jQuery in TDD
For our example we'll develop an area of a page where a check box shows or hides a field set when it is clicked.
Build the Test Fixture
Step one is setting up a QUnit test fixture using jQuery's CDN to run our tests against:
In this, sample.tests.js contains our tests driving the code developed in sample.js which will actually perform the work we care about. In good TDD fashion, we could open this, see catastrophe and determine that our first step is to start writing our actual tests.
Our next step is to create sample.tests.js, and start it with our global variable and QUnit module declarations.
/*global module,test*/
var unit, $;
module('sample', {
setup: function () {
'use strict';
}
});
Here, unit is our unit under test, $ is our mock of jQuery and the comment at the top is a note to JSHint that we will be using the global names module and test from QUnit.
What Do We Want To Do?
Let's outline our test, to capture our intent and help us organize our thoughts:
test('clicking checkbox displays fieldset', function () {
'use strict';
});
Dissecting that further, what we expect that to mean in terms of testable events is:
The checkbox will be selected
A callback will be bound to the checkbox's click event which will
Select the fieldset
Toggle it to display or hide
Formalizing these expectations as asserts which will pass or fail we get:
var foundSelector,
foundClickCallback = null,
toggleCalled = false;
equal(foundSelector, '#uxShowFieldset', 'Checkbox selected by expected ID');
notEqual(foundClickCallback, null, 'Callback set for checkbox click');
// Asserts for callback
equal(foundSelector, '#uxFieldset', 'Fieldset selected by expected ID');
ok(toggleCalled, 'Toggle was called');
Next we'll mock jQuery as we expect it to be used in the implementation and capture the values that we're testing in our asserts:
// After our variable declaration
$ = function (selector) {
foundSelector = selector;
};
$.click = function (callback) {
foundClickCallback = callback;
};
$.toggle = function () {
toggleCalled = true;
};
Lastly we need to perform the actions to test with our mock jQuery injected into it:
// After defining our mock of jQuery
unit = new Sample($);
unit.init();
// Before our second set of asserts
foundClickCallback();
Note: I would ordinarily implement an init method as an IIFE within the constructor. I'm not doing that here, so that we can implement several alternatives for comparison.
Passing the Test
Following proper TDD practices, we will go through the following steps (running and failing the tests between each):
Create the sample.js file
Write the class function, Sample
Write the init function
Implement calls to jQuery to pass our asserts
If our tests are written well, the code that passes our test will work on the page. However, an astute reader might have already noticed that our tests are not well-written for jQuery. Consider the following implementation which will pass our test:
Our test assumed that our implementation would follow typical jQuery conventions and chain selectors to subsequent actions. If we were to do that our test would definitely pass, but my point here is that a developer could write broken code that doesn't pass our test -- and so we must account for that in our test.
Enforcing Good jQuery Usage
To account for that, we need to update our test so that, instead of finding specific selectors and asserting their correctness, it emulates jQuery's ability to chain by returning objects with click and toggle methods that we confirm are called.
In this mock, we only return an object with testable click and toggle methods for the exact instances when we expect them to be used -- to the point that the click method sets a flag to return the toggle-able object to guarantee that it is being called in response to the click event. This should force the developer to use jQuery correctly!
We have some new names in our mock, so our updated variable declaration becomes:
A final note on this mock is that one clear benefit is how we only need to assert that the field set was toggled, because that one test implies that everything else in the chain must have succeeded.
So the totality of our asserts becomes:
ok(fieldsetToggled, 'Fieldset was toggled on checkbox click');
Now we're ready to run it against our original implementation and see it fail! Once you've gotten that out of your system, point the test to a new function so that we can save the old implementation for comparison:
unit.init2();
Following the TDD process again, this test now enforces an implementation that works -- and looks like something you might see in practice:
What this example demonstrates is how natural it is to work through a test and assume jQuery will drive the implementation -- did you question for a second why we were injecting jQuery? -- and that ensure a good jQuery implementation properly you have to emulate its chaining in your mock.
Test Outcomes
The result of having tests that are so aware of jQuery is that their passage depends on the developer writing a specific implementation regardless of whether the outcome is correct or not.
For example, our current test will will fail if we refactor to use jQuery's on binding instead of click,
both of which are examples that improve our code, but will fail our test because the mock is, and has to be, so rigid. Which leads us to a...
New WebDev Law: A test specific enough to enforce something is done right mocking jQuery, is specific enough to enforce it being done just one way.
Working with tests that mock jQuery will condition a developer to expect tests to fail and require updating after any change in implementation, undermining the core assumption of TDD: the tests are correct.
View Façade
To avoid this, we can write a view façade which serves our class with exactly the functionality it needs -- in its terms -- and hides the jQuery used to provide it.
Revisiting our example, let's write a new test with this in mind.
We'll start with our first instincts and restate the asserts from the first test in the example, but in a non-jQuery way (i.e. no selectors):
var checkbox = {},
foundCheckbox,
foundClickCallback = null,
fieldset = {},
foundFieldset,
toggleCalled;
equal(foundCheckbox, checkbox, 'Correct checkbox found for binding click event');
notEqual(foundClickCallback, null, 'Callback for click event provided');
// Asserts for callback
equal(foundFieldset, fieldset, 'Correct fieldset found for binding click event');
ok(toggleCalled, 'Fieldset was toggled');
Now we'll mock out what we expect this new view façade to provide by appending a new facade to the variable declaration:
For this example, I've chosen to keep it simple by directly translating what our asserts expect into the methods that façade provides; this approach isn't required, what matters is that the façade serves the view terms that make sense within its context.
Note: the fact that toggleCalled is true is implied by foundFieldset being equal to fieldset, so we can refactor to remove toggleCalled.
The final step to building our test is to perform the actions to test with our mock façade:
// After defining our mock of the view facade
unit = new Sample(facade);
unit.init3();
// Before our second set of asserts
foundClickCallback();
Once we work through our implementation, it should look something like this (if the façade is called view locally):
view.bindClick(
view.getCheckbox(),
function () {
view.toggleElement(view.getFieldset());
}
);
By completely hiding jQuery from our Sample class, any changes to how the implementation within the view façade uses jQuery won't require an update to our tests.
jQuery is a DAL
This sort of encapsulation should seem familar to any developer who's worked in layered applications, because this is essentially the same sort of task-based abstraction applied by a business layer over a data layer. This isn't an accident, because jQuery, as the intermediary for reading from and writing to the DOM, is a data layer (among otherthings) -- we just don't typically see it that way, because it sits at the very opposite end of the stack from the traditional data layer.
That Solves Everything (Except the Original Problem)
Now we can avoid complicated jQuery mocks that make the tests for our view code fragile. The only cost is an additional small layer that we have to implement ... which works directly with jQuery.
Won't we have the exact same problem when we write tests for the view façade?
I'll post how to test the view façade while avoiding the same pitfalls in the next installment.
As described in my later post, My "View Adapter" Was Just a Façade, I updated this post to refer to a "view façade" instead of a "view adapter" as it had originally been written.
Before moving to the product engineering department in my company, I had spent my professional career in services organizations supporting, implementing and, eventually, designing custom software systems for use by a single customer. In that time, with few exceptions, that meant following the Waterfall methodology in an attempt to assess all of a customer's requirements in advance so that we could make a (futile) attempt to produce an accurate time and cost estimate.
Waterfall
For the fortunate uninitiated, Waterfall is a design process borrowed from (and better suited to) non-software engineering projects. In Waterfall, the project team follows a clear sequence from interviewing stakeholders through documenting project requirements, designing a comprehensive implementation, executing it, performing internal and stakeholder QA, and finally to deploying. There are many variations of it, but the intent of each version is to avoid late changes (e.g. after the satellite is already in orbit) that could be prohibitively expensive.
As for the name, I think it is meant to connote the process's steady progression. In my experience, it also does very well at capturing the sense of knowing it will end by plunging headlong into a free fall before a rocky conclusion.
My part in this process was most recently as an architect. I took the requirements for a project and outlined the pages, components, services, code libraries, and integration points with third-party software and services that would fulfill those requirements.
UML
Toward the end of my stint as an architect I decided to revisit UML for the first time since college, as part of a constant effort to make Waterfall work for me. Not to spell out every detail, UML is essentially a common set of diagramming stencils which provide a consistent model for defining a system's components, interactions and behaviors. What makes this more impressive than a run-of-the-mill Visio or whiteboard diagram, is that, as a standard modeling language, it is defined well enough to be machine-readable so that tools can generate "skeleton" code from and validate programmer code against them.
The reason to seek out a standard modeling language was to avoid the needless problems of inventing my own design notation -- which, by this time, had become embarrassingly mature. I was returning to UML specifically, because a diagramming "language" can represent a lot of detail concisely. This is important, because Waterfall has a way of requiring more and more detail as you adapt over time to avoid repeating the shortcomings of past projects.
However before I could incorporate this into my documentation (and certainly before I could get budget approval for a $600 piece of software), I moved into product engineering and away from Waterfall. Good riddance!
TDD + AI
In engineering I have been inculcated in TDD (see my previous post for a demonstration of it and my new found zealousness for it). As my professional fixation shifted from UML to TDD, those two concepts collided in the transition and gave me a new idea: if UML defines a system down to the unit and TDD defines how a unit will operate successfully before it's written, wouldn't it be a relatively small step to automate writing the code to pass the tests and implement the whole system?
This isn't getting a roomful of monkeys to type out all of Shakespeare's 38 plays in order, just a collection of his sonnets individually.
As ridiculous as I make that sound, I don't consider it at all outside the realm of possibility, just of reasonableness for all but the most large projects -- and even then, that's only because this technology doesn't exist yet. To my knowledge, we're missing a standard for tightly associating tests to diagrams in UML, and we're certainly without a tool that can process such a TD-UML diagram to produce a full implementation.
As you consider this, let me reply, "yes, this would produce less efficient code ... at first."
Today unit tests can be failed that don't perform to certain non-functional standards (e.g. performance), and a hypothetical automation tool could continue to iterate over different implementations even after passing a test in order to discover faster variations. Ultimately, just as it's rare to find a developer anymore who insists on writing their own assembly for performance reasons, I think we could expect this to eventually produce as fast, or faster, running code than a person could.
As for whether this would produce less elegant code, that plays into why I'm excited about this idea instead of being worried. But that's a topic for another day.