Unittesting JavaScript with Evidence

Loading...

Flash Player 9 (or above) is needed to view presentations.
We have detected that you do not have it on your computer. To install it, go here.

1 comments

Comments 1 - 1 of 1 previous next Post a comment

Post a comment
Embed Video
Edit your comment Cancel

14 Favorites

Unittesting JavaScript with Evidence - Presentation Transcript

  1. Unittesting JavaScript with Evidence Tobie Langel @tobie on twitter
  2. “Evidence in its broadest sense includes everything that is used to determine or demonstrate the truth of an assertion.” –Wikipedia
  3. http://github.com/tobie/evidence
  4. Yet Another Unit Test Framework
  5. DOH Test.Simple/More JSUnit TestCase QUnit jsUnitTest Crosscheck JSTest J3Unit jsUnity JSNUnit RhinoUnit YUI Test FireUnit JSSpec ... unittest.js JSpec screw-unit
  6. Why?
  7. Scratching our own itch
  8. A lot of unit tests (> 2000 assertions).
  9. A lot of unit tests (> 2000 assertions). Complex cases (async, cross-browser).
  10. A lot of unit tests (> 2000 assertions). Complex cases (async, cross-browser). Uses a framework originally created in 2005 for script.aculo.us...
  11. A lot of unit tests (> 2000 assertions). Complex cases (async, cross-browser). Uses a framework originally created in 2005 for script.aculo.us... ...with a dependency on Prototype.
  12. #1 Framework agnostic
  13. #2 Environment agnostic
  14. #2 Environment agnostic (Attempts to run in as many different environments as possible.)
  15. #3 Self-contained
  16. #3 Self-contained (Doesn’t pollute the global scope.)
  17. #4 Reliable
  18. #5 Built with async in mind
  19. #6 Easier to automate
  20. #7 Promotes better testing
  21. “I recommend that developers spend 25-50% of their time developing tests.” –Kent Beck
  22. Drawing by Witold Riedel (http://www.witoldriedel.com)
  23. Kent Beck’s original white paper Drawing by Witold Riedel (http://www.witoldriedel.com)
  24. Kent Beck’s original white paper http://tr.im/kentbeck Drawing by Witold Riedel (http://www.witoldriedel.com)
  25. JUnit
  26. JUnit Kent Beck & Erich Gamma
  27. Test::Unit
  28. MiniTest
  29. unittest / PyUnit
  30. YUI.Test
  31. ... and of course, Kent Beck’s original Smalltalk implementation.
  32. (
  33. Evidence
  34. !=
  35. RSpec
  36. BDD
  37. Why?
  38. I don’t like BDD
  39. &
  40. JavaScript’s awful at writing DSLs
  41. unless
  42. you’re using it’s bad parts!
  43. function callAssertions(fn) { var rgxp = /^[^{]*{((.*n*)*)}/m; fn = fn.toString(); fn = fn.match(rgxp)[1]; eval('with (dsl) { with (context) { with (matchers) { ' + fn + ' }}}'); }
  44. Function decompilation function callAssertions(fn) { (Not part of any standard.) var rgxp = /^[^{]*{((.*n*)*)}/m; fn = fn.toString(); fn = fn.match(rgxp)[1]; eval('with (dsl) { with (context) { with (matchers) { ' + fn + ' }}}'); }
  45. function callAssertions(fn) { var rgxp = /^[^{]*{((.*n*)*)}/m; fn = fn.toString(); fn = fn.match(rgxp)[1]; eval('with (dsl) { with (context) { with (matchers) { ' + fn + ' }}}'); Nested with statements } (Not ES5 strict compliant.)
  46. function callAssertions(fn) { var rgxp = /^[^{]*{((.*n*)*)}/m; fn = fn.toString(); eval fn = fn.match(rgxp)[1]; (Not supported in some environments.) eval('with (dsl) { with (context) { with (matchers) { ' + fn + ' }}}'); }
  47. function callAssertions(fn) { var rgxp = /^[^{]*{((.*n*)*)}/m; fn = fn.toString(); fn = fn.match(rgxp)[1]; eval('with (dsl) { with (context) { with (matchers) { ' + fn + ' }}}'); }
  48. )
  49. xUnit
  50. xUnit
  51. xUnit TestCase
  52. xUnit TestCase TestSuite
  53. xUnit TestCase TestSuite TestRunner
  54. xUnit TestCase TestSuite TestRunner TestResult
  55. Anatomy of a TestCase
  56. var ArrayTest = Evidence.TestCase.extend('ArrayTest', { setUp: function() { this.array = ['foo', 'bar', 'baz']; }, testFirst: function() { this.assertEqual('foo', _.first(this.array)); this.assertUndefined(_.first([])); }, testLast: function() { this.assertEqual('bar', _.last(this.array), 'Failed to grab the last element of the array.'); this.assertUndefined(_.last([])); } });
  57. var ArrayTest = Evidence.TestCase.extend('ArrayTest', { setUp: function() { this.array = ['foo', 'bar', 'baz']; }, “Subclass” TestCase testFirst: function() { this.assertEqual('foo', _.first(this.array)); this.assertUndefined(_.first([])); }, testLast: function() { this.assertEqual('bar', _.last(this.array), 'Failed to grab the last element of the array.'); this.assertUndefined(_.last([])); } });
  58. var ArrayTest = Evidence.TestCase.extend('ArrayTest', { setUp: function() { this.array = ['foo', 'bar', 'baz']; }, Give it a name. testFirst: function() { this.assertEqual('foo', _.first(this.array)); this.assertUndefined(_.first([])); }, testLast: function() { this.assertEqual('bar', _.last(this.array), 'Failed to grab the last element of the array.'); this.assertUndefined(_.last([])); } });
  59. var ArrayTest = Evidence.TestCase.extend('ArrayTest', { setUp: function() { this.array = ['foo', 'bar', 'baz']; }, testFirst: function() { Add fixtures to your _.first(this.array)); this.assertEqual('foo', setUp method. this.assertUndefined(_.first([])); }, testLast: function() { this.assertEqual('bar', _.last(this.array), 'Failed to grab the last element of the array.'); this.assertUndefined(_.last([])); } });
  60. var ArrayTest = Evidence.TestCase.extend('ArrayTest', { Prefix setUp:testcases {with your function() this.array = ['foo', 'bar', 'baz']; }, test. testFirst: function() { this.assertEqual('foo', _.first(this.array)); this.assertUndefined(_.first([])); }, testLast: function() { this.assertEqual('bar', _.last(this.array), 'Failed to grab the last element of the array.'); this.assertUndefined(_.last([])); } });
  61. var ArrayTest = Evidence.TestCase.extend('ArrayTest', { setUp: function() { this.array = ['foo', 'bar', 'baz']; }, testFirst: function() { this.assertEqual('foo', _.first(this.array)); this.assertUndefined(_.first([])); }, testLast: your assertions. Write function() { this.assertEqual('bar', _.last(this.array), 'Failed to grab the last element of the array.'); this.assertUndefined(_.last([])); } });
  62. var ArrayTest = Evidence.TestCase.extend('ArrayTest', { setUp: function() { this.array = ['foo', 'bar', 'baz']; }, testFirst: function() { this.assertEqual('foo', _.first(this.array)); this.assertUndefined(_.first([])); }, testLast: function() { this.assertEqual('bar', _.last(this.array), 'Failed to grab the last element of the array.'); this.assertUndefined(_.last([])); } Optionally add }); meaningful error messages.
  63. Built-in async handling.
  64. Evidence.TestCase.extend('AjaxTest', { testAjaxRequest: function(testcase) { testcase.pause(); new Ajax.Request('/some/url', { onComplete: function(response) { testcase.resume(function() { this.assert(response); }); } }); } });
  65. Evidence.TestCase.extend('AjaxTest', { testAjaxRequest: function(testcase) { testcase.pause(); new Ajax.Request('/some/url', { instance TestCase onComplete: function(response) { conveniently passed as a testcase.resume(function() { this.assert(response); first argument. }); } }); } });
  66. Evidence.TestCase.extend('AjaxTest', { testAjaxRequest: function(testcase) { No need to bind this or testcase.pause(); create your own closure. new Ajax.Request('/some/url', { onComplete: function(response) { testcase.resume(function() { this.assert(response); }); } }); } });
  67. Evidence.TestCase.extend('AjaxTest', { testAjaxRequest: function(testcase) { testcase.pause(); new Ajax.Request('/some/url', { onComplete: function(response) { testcase.resume(function() { this.assert(response); }); Bound to the original } }); scope so: } this === testcase });
  68. The TestSuite
  69. function getTestCaseNames(testcaseClass) { var results = []; for (var property in testcaseClass.prototype) { if (property.indexOf('test') === 0) { results.push(property); } } return results.sort(); }
  70. Grab all the methods function getTestCaseNames(testcaseClass) { var results = []; starting with test. for (var property in testcaseClass.prototype) { if (property.indexOf('test') === 0) { results.push(property); } } return results.sort(); }
  71. function getTestCaseNames(testcaseClass) { var results = []; for (var property in testcaseClass.prototype) { if (property.indexOf('test') === 0) { results.push(property); } Sort the results. } return results.sort(); }
  72. function loadTestsFromTestCase(testcaseClass) { var suite = new TestSuite(testcaseClass.displayName), methodNames = getTestCaseNames(testcaseClass); for (var i = 0; i < methodNames.length; i++) { suite.push(new testcaseClass(methodNames[i])); } return suite; }
  73. Create a new suite. function loadTestsFromTestCase(testcaseClass) { var suite = new TestSuite(testcaseClass.displayName), methodNames = getTestCaseNames(testcaseClass); for (var i = 0; i < methodNames.length; i++) { suite.push(new testcaseClass(methodNames[i])); } return suite; }
  74. Give it a name. function loadTestsFromTestCase(testcaseClass) { var suite = new TestSuite(testcaseClass.displayName), methodNames = getTestCaseNames(testcaseClass); for (var i = 0; i < methodNames.length; i++) { suite.push(new testcaseClass(methodNames[i])); } return suite; }
  75. function loadTestsFromTestCase(testcaseClass) { var suite = new TestSuite(testcaseClass.displayName), methodNames = getTestCaseNames(testcaseClass); for (var i = 0; i < methodNames.length; i++) { suite.push(new testcaseClass(methodNames[i])); } Grab the testcase names. return suite; }
  76. function loadTestsFromTestCase(testcaseClass) { var suite = new TestSuite(testcaseClass.displayName), methodNames = getTestCaseNames(testcaseClass); for (var i = 0; i < methodNames.length; i++) { suite.push(new testcaseClass(methodNames[i])); } return suite; Create a new instance of } the testcase class.
  77. function loadTestsFromTestCase(testcaseClass) { var suite = new TestSuite(testcaseClass.displayName), methodNames = getTestCaseNames(testcaseClass); for (var i = 0; i < methodNames.length; i++) { suite.push(new testcaseClass(methodNames[i])); } return suite; Add it to your suite. }
  78. Benefits
  79. Custom assertions
  80. Evidence.TestCase.extend('ElementTest', { setUp: function() { this.element = document.createElement('div'); }, testElementExtend: function() { var extended = Element.extend(this.element); this.assertEqual(this.element, extended); this.assertRespondsTo('show', extended); // ... }, testElementClone: function() { var cloned = Element.clone(this.element); this.assert(cloned.show); // is extended // ... } });
  81. Evidence.TestCase.extend('ElementTest', { setUp: function() { this.element = document.createElement('div'); }, What!? testElementExtend: function() { var extended = Element.extend(this.element); this.assertEqual(this.element, extended); this.assertRespondsTo('show', extended); // ... }, testElementClone: function() { var cloned = Element.clone(this.element); this.assert(cloned.show); // is extended // ... } });
  82. Evidence.TestCase.extend('ElementTest', { setUp: function() { this.element = document.createElement('div'); }, testElementExtend: function() { var extended = Element.extend(this.element); this.assertEqual(this.element, extended); this.assertRespondsTo('show', extended); // ... }, Again?! testElementClone: function() { var cloned = Element.clone(this.element); this.assert(cloned.show); // is extended // ... } });
  83. Evidence.TestCase.extend('ElementTest', { setUp: function() { this.element = document.createElement('div'); }, testElementExtend: function() { var extended = Element.extend(this.element); this.assertEqual(this.element, extended); this.assertRespondsTo('show', extended); // ... }, testElementClone: function() { var cloned = Element.clone(this.element); this.assert(cloned.show); // is extended // ... OK, now I get it. } });
  84. != DRY
  85. Evidence.TestCase.extend('ElementTest', { //... assertElementExtended: function(element, message) { this._assertExpression( typeof element.show === 'function', message || 'Element is not extended.', 'Expected %o to be extended.', element ); }, testElementExtend: function() { var extended = Element.extend(this.element); this.assertElementExtended(extended); }, testElementClone: function() { var cloned = Element.clone(this.element); this.assertElementExtended(cloned); }
  86. Evidence.TestCase.extend('ElementTest', { //... assertElementExtended: function(element, message) { this._assertExpression( typeof element.show === 'function', ! message || 'Element is not extended.', 'Expected %o to be extended.', element ); Syntax still subject to }, change! testElementExtend: function() { var extended = Element.extend(this.element); this.assertElementExtended(extended); }, testElementClone: function() { var cloned = Element.clone(this.element); this.assertElementExtended(cloned); }
  87. Instance methods
  88. function getInnerHTML(element) { var html = element.innerHTML.toString(); return html.toLowerCase().gsub(/[rnt]/, ''); } Evidence.TestCase.extend('ElementTest', { // ... testElementInsert: function() { var html = '<p>a paragraph</p>'; Element.insert(this.element, html); this.assertEqual(html, getInnerHTML(this.element)); } });
  89. function getInnerHTML(element) { var html = element.innerHTML.toString(); return html.toLowerCase().gsub(/[rnt]/, ''); } Global scope pollution. Evidence.TestCase.extend('ElementTest', { // ... testElementInsert: function() { var html = '<p>a paragraph</p>'; Element.insert(this.element, html); this.assertEqual(html, getInnerHTML(this.element)); } });
  90. function getInnerHTML(element) { var html = element.innerHTML.toString(); return html.toLowerCase().gsub(/[rnt]/, ''); } Obscure syntax. What’s wrong with Evidence.TestCase.extend('ElementTest', { //this.element.innerHTML? ... testElementInsert: function() { var html = '<p>a paragraph</p>'; Element.insert(this.element, html); this.assertEqual(html, getInnerHTML(this.element)); } });
  91. Evidence.TestCase.extend('ElementTest', { // ... testElementInsert: function() { var html = '<p>a paragraph</p>'; Element.insert(this.element, html); this.assertEqual(html, this.getElementHTML()); }, getElementHTML: function() { var html = this.element.innerHTML.toString(); return html.toLowerCase().gsub(/[rnt]/, ''); } });
  92. Evidence.TestCase.extend('ElementTest', { // ... Instance method! testElementInsert: function() { var html = '<p>a paragraph</p>'; Element.insert(this.element, html); this.assertEqual(html, this.getElementHTML()); }, getElementHTML: function() { var html = this.element.innerHTML.toString(); return html.toLowerCase().gsub(/[rnt]/, ''); } });
  93. Evidence.TestCase.extend('ElementTest', { // ... testElementInsert: function() { var html = '<p>a paragraph</p>'; Element.insert(this.element, html); this.assertEqual(html, this.getElementHTML()); }, getElementHTML: function() { Would benefit from a var html = this.element.innerHTML.toString(); return html.toLowerCase().gsub(/[rnt]/, ''); custom assertion too. } });
  94. The TestRunner
  95. var suite = loadTestsFromTestCase(ArrayTest); var runner = new Evidence.Runner(); var results = runner.run(suite);
  96. The TestResult
  97. Logs to the console. The TestResult
  98. Logs to the console. The TestResult Prints to STDOUT.
  99. Displays the results in Logs to the console. a web page. The TestResult Prints to STDOUT.
  100. Displays the results in Logs to the console. a web page. The TestResult Sends the data back to the server in JSON Prints to STDOUT. (or XML).
  101. The Magic
  102. All of this is good to know,
  103. but it’s tedious.
  104. So...
  105. Evidence handles it for you!
  106. Evidence.TestCase.extend('ArrayTest', { setUp: function() { this.array = ['foo', 'bar', 'baz']; }, testFirst: function() { this.assertEqual('foo', _.first(this.array)); this.assertUndefined(_.first([])); }, testLast: function() { this.assertEqual('bar', _.last(this.array), 'Failed to grab the last element of the array.'); this.assertUndefined(_.last([])); } });
  107. It’s all you need!
  108. ? @tobie on twitter http://github.com/tobie/evidence (Soon hopefully on http://evidencejs.org)

+ Tobie LangelTobie Langel, 3 weeks ago

custom

1709 views, 14 favs, 1 embeds more stats

Evidence is a new, framework-agnostic unit testing more

More info about this document

© All Rights Reserved

Go to text version

  • Total Views 1709
    • 1695 on SlideShare
    • 14 from embeds
  • Comments 1
  • Favorites 14
  • Downloads 34
Most viewed embeds
  • 14 views on http://speakerrate.com

more

All embeds
  • 14 views on http://speakerrate.com

less

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate. If needed, use the feedback form to let us know more details.

Cancel
File a copyright complaint
Having problems? Go to our helpdesk?

Categories