More Related Content Similar to Unit-Testing Your Legacy JavaScript Similar to Unit-Testing Your Legacy JavaScript (20) Unit-Testing Your Legacy JavaScript1. 23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
1
Unit-Testing Your
Legacy
JavaScript
Rob Myers & Lars Thorup
for
Mile High Agile 2013
19 Apr 2013
2. 23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
2
3. characterization tests
• We don‟t add or change behavior.
• We‟re not bug-hunting.
• We aim for coverage.
• We must see GREEN.
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
3
4. technique:
“The Three Questions”
• What behavior needs test coverage?
• What‟s preventing us from writing a
test?
• What can we do about that?
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
4
5. MessTrek lab, part 1:
list scenarios that need testing
• Let‟s focus on game.js.
• Look for behavior that needs testing.
Write a description of as many
scenarios as you can find.
• Also record anything you notice that
will make testing difficult.
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
5
6. technique:
separate code from data
• It‟s easier to maintain JavaScript
when it‟s not mixed up with HTML.
• Use „Name Anonymous Function‟ as a
first step, if necessary.
• Then move the function into a .js file
associated with the .html file.
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
6
7. before
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
7
<script>
$(function() {
$('.new_address_form').hide();
$('.countrySelect').change(function() {
$('.new_address_form').hide();
var country = $(".countrySelect").val();
if (country === "JP") {
$('.address_jp').show();
}
else {
$('.address_generic').show();
}
});
});
</script>
8. after „Name Anonymous Function‟
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
8
<script>
$(function() {
$('.new_address_form').hide();
$('.countrySelect').change(onCountryChange);
});
function onCountryChange() {
$('.new_address_form').hide();
var country = $(".countrySelect").val();
if (country === "JP") {
$('.address_jp').show();
}
else {
$('.address_generic').show();
}
}
</script>
9. technique:
pass globals into the method
• It‟s easier to test a JavaScript method‟s
various behaviors when it can operate on all
or part of a document.
• First use „Pass Whole Document‟.
• Then have your unit tests pass in minimal
DOM fragments (by varying <qunit-fixture>
as shown earlier) to test discrete behaviors.
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
9
10. before
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
10
<script>
$(function() {
$('.new_address_form').hide();
$('.countrySelect').change(onCountryChange);
});
function onCountryChange() {
$('.new_address_form').hide();
var country = $(".countrySelect").val();
if (country === "JP") {
$('.address_jp').show();
}
else {
$('.address_generic').show();
}
}
</script>
11. after „Pass Whole Document‟
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
11
<script>
$(function() {
$('.new_address_form').hide();
$('.countrySelect').change(
function () { onCountryChange(document); });
});
function onCountryChange(form) {
$('.new_address_form’, form).hide();
var country = $(".countrySelect”, form).val();
if (country === "JP") {
$('.address_jp’, form).show();
}
else {
$('.address_generic’, form).show();
}
}
</script>
12. technique:
separate behavior from initialization
• It‟s easier to test behavior when not
coupled to initialization code.
• Use „Extract Method‟ to preserve
existing interfaces.
• You then have a “seam” where you
can test by injecting mocks.
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
12
13. before
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
13
var openOrderReport = function(output, accountNumber) {
var account = LunExData.queryAccount(accountNumber);
var openOrders =
LunExData.queryOrders("open", account, new Date());
for (var i = 0; i < openOrders.length; ++i) {
var order = openOrders[i];
// ...
output.Write("Order ...");
// ...
}
}
14. after „Extract Method‟
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
14
var openOrderReport = function(output, accountNumber) {
var account = LunExData.queryAccount(accountNumber);
var openOrders =
LunExData.queryOrders("open", account, new Date());
formatOpenOrderReport(output, account, openOrders);
}
var formatOpenOrderReport =
function(output, account, openOrders) {
for (var i = 0; i < openOrders.length; ++i) {
var order = openOrders[i];
// ...
output.Write("Order ...");
// ...
}
}
15. technique:
mock dependencies
• Results (and test performance) often depend
heavily on externals, yet we want
fast, predictable results.
• You can easily stub out the behaviors of
dependencies and libraries.
• You are then characterizing the particular
behaviors of your code, not external libraries.
• To avoid impacting other tests, be sure to undo
the stub after each test.
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
15
16. sinon.js basics
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
16
// to stub a behavior, e.g., Math.random()
sinon.stub(Math, 'random', function () { return 0.5; });
// to undo the stub
Math.random.restore();
17. MessTrek lab, part 2:
write a characterization test
• Start in (and be inspired by) test/game.test.html
• Focus on weapons.
• Use safe, careful refactorings where necessary.
• Do as little refactoring as possible.
• For now just get one characterization test to
pass, then let us know you are DONE!
• Be sure to read and understand the rules on the
next slide…
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
17
18. a few rules for part 2
• You must not alter (or add to) anything in the
Untouchables folder
(userInterface.js, sampleclient.html).
• You must not alter the results of anything in the
Untouchables folder (sampleclient.html).
23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
18
19. 23 May 2013
© 2008-2013 Agile Institute.
All Rights Reserved.
19
reflection
20. 23 May 2013
© 2008-2013 Agile Institute. All
Rights Reserved.
20
lars@zealake.com
http://www.zealake.com/blog/
@larsthorup
Rob.Myers@agileInstitute.com
http://PowersOfTwo.agileInstitute.com/
@agilecoach
Editor's Notes This isn’t TDD. But you won’t be able to proceed with TDD without these techniques.If we find a bug, record it: In a bug tracker, or as a story.Not isolation. These are not “unit tests” - These tests can cover as much behavior as possible (though they must still be deterministic and pretty fast). And they don’t have to be pretty!characterization tests should pass.The test is trying to “pin” behavior so we can refactor further. What needs testing? now move the newly named function into a .js fileTODO: one more, where we pass in the DOM element to make it more testable in various circumstances, with various fixtures now move the newly named function into a .js fileTODO: one more, where we pass in the DOM element to make it more testable in various circumstances, with various fixtures What needs testing? What needs testing? Very easyLean on the compiler Under TDD Resources\\JavaScript\\lib Open the MessTrek files and look at them.What needs testing?What’s stopping you?If you get stuck, there is another set of code available, with tests. Read over the tests and become familiar with the techniques used. Note that the code is still very ugly. sampleclient.html depends on Game’s interface, and represents your customer’s UI developers. UserInterface represents some third-party vendor, and you *can’t* change the code! Thank you!