Test-Driven JavaScript with ScrewUnit & BlueRidge Andy Peterson Jonah Williams Carbon Five
Introductions
Carbon Five Boutique software consulting 20 people, all developers Agile development, collaboration, transparency Mix of projects: start-ups / non-profits / enterprise History of Java, but now RoR + misc.
Agenda JavaScript unit testing: how & why BDD, jQuery, plugins Demo TDD with a simple test Problems & lessons learned Q&A
Why NOT JS Unit Testing? Can’t get started We don’t; nobody else does Patterns unclear-- what does it look like Too many tools (and arch.) to choose from ... lack of tool & infrastructure support Flakey/bad experiences
Why JS Unit Testing? Tests => Confidence => Ambition Take on more challenging requirements More homogenous architecture (w/o Flash) Push logic into browser... scalability Refactor reliably
JS Testing Universe Styles of Tests: xUnit: JSUnit, QUnit, YUITest, etc. BDD: JSSpec, ScrewUnit Automation: In-browser w/ Selenium, WebDriver, JSTestDriver Out of browser: Env.js + Rhino
Blue Ridge Ruby on Rails plugin (therefore conventions!) Integrates ScrewUnit (BDD) and Smoke (mocking) Glue code and scripts Rhino & Env.js to run out of browser from Larry Karnowski, Runcoderun
BDD Language closer to user stories Popularized by RSpec in Ruby describe -- organize before / after blocks Nesting describes -- to DRY One assert should per test example
describe Stack do before(:each) do @stack = Stack.new @stack.push :item end describe "#peek" do it "should return the top element" do @stack.peek.should == :item end it "should not remove the top element" do @stack.peek @stack.size.should == 1 end end describe "#pop" do Stack (generated docs) it "should return the top element" do #peek @stack.pop.should == :item - should return the top element end it "should remove the top element" do - should not remove the top @stack.pop element @stack.size.should == 0 #pop end - should return the top element end end - should remove the top element
BDD in RSpec describe 'a man' { before { @man = Man.new :luck=>5 } describe '#decrement_luck' { it "decrements the luck field by the given amount" { @man.decrement_luck(3) @man.luck.should == 2 } } ... });
BDD in ScrewUnit describe('Man', function() { var man; before(function() { man = new Man({luck: 5}); }); describe('#decrement_luck', function() { it("decrements the luck field by the given amount", function() { man.decrement_luck(3); expect(man.luck()).to(equal, 2) }); }); ... });
Let’s See It
Wizard-izer (our example) Given a form comprised of fieldsets Creates a “panel” for each fieldset Hides all but the first panel Adds buttons to navigate panels etc.
Blue Ridge Installation http://github.com/relevance/blue-ridge/tree/master # Within a rails project > ./script/plugin install git:// github.com/relevance/blueridge.git > ./script/generate blue_ridge > ./script/generate javascript_spec wiz
Maven Installation http://code.google.com/p/javascript-test-maven-plugin/ <plugin> <executions><execution> <goals> <goal>javascript-test</goal> </goals> </execution></executions> <groupId>com.carbonfive.javascript-test</groupId> <artifactId>javascript-test-maven-plugin</artifactId> <version>1.0-beta1</version> <configuration> <includes> <include>src/test/javascript/suite*.html</include> </includes> </configuration> </plugin>
3 Components Fixture - markup that JS requires test/javascript/widget.html Spec - tests test/javascript/specs/widget_spec.js) Code to test - independent JS <public>/javascript/widget.js
jQuery Functional DOM manipulation One function $: $(selector) eg. $(‘p.cls’) Chainable -- $ (‘p.cls’).addClass(‘abc’).removeClass(‘cls’).etc(...) Plugins
jQuery Plugin Add function to jQuery.fn.reverse() = function() { jQuery object // “this” is list of nodes Receive list of nodes $(this).each(function() { var t = $(this).text(); Do your stuff $(this).text(t); } Return “this” return this; Good tutorials online }
DOM cleanup Tools don’t reset your DOM automatically They are writing status back to the page (yuck!) Solution Wrap fixtures (in #fixture container) before function that restores DOM state
DOM cleanup fn // before(function() { $('#fixture).fixture(); }); jQuery.fn.fixture = function() { this.each(function() { if (this.original) { $(this).html(this.original); } else { this.original = $(this).html(); } }); }
Put CSS in Fixture Helps with debugging Use FireBug to inspect
Structure as a jQuery plugin jQuery is not required-- but really helps you organize code Separate document.ready() from code-- dependency injection Use as organizing principal, and dependency injection Write modular code, instead page-specific code
Learnings
Not Hard to Make JS Testing a Part of Your Process
Markup Design Generated in JS? On the server? Hybrid? Unit testing encourages generating more markup in JavaScript Will affect SEO and accessibility (good or bad)
When to test Some JS can be experimental Test-drive when you have a good sense of what he component is Testing is a refactoring tool As code gets complicated, pull into testable units
Don’t Be Afraid of JS Reminder wizard Method chooser demo-- data driven JS, with ajax structure
What to Unit Test / Limitations AJAX JavaScript Unit Testing setTimeout() setInterval() DOM Business Manipulation Logic Animations Computed sizes
It’s Not Browser Testing ...it’s JS unit testing Not cross-browser testing Not your production markup Functional test framework still have a place
Our Testing Stack Selenium Integration & Browser Testing 1/10th Controller/View Integration Tests 1/5th Javascript Unit Testing Model Unit Controller Unit 1/10th Tests Tests
JavaScript Testing Yes We Can! Andy Peterson Jonah Williams andy@carbonfive.com jonah@carbonfive.com skype: ndpsoft
Blue Ridge TODOs Run in more browsers (not just Firefox) Speed Events Driving in multiple browsers Using code served from app Shenandoah
Other Options ScrewUnit Server Selenium + JSUnit testswarm (?)
References (Bibliography) http://github.com/relevance/blue-ridge http://pivotallabs.com/users/nick/blog/articles/455- better-javascript-testing-through-screwunit http://github.com/ndp/wizardize http://www.slideshare.net/jeresig/understanding- javascript-testing http://code.google.com/p/javascript-test-maven- plugin/

Javascript unit testing, yes we can e big

  • 1.
    Test-Driven JavaScript with ScrewUnit& BlueRidge Andy Peterson Jonah Williams Carbon Five
  • 2.
  • 3.
    Carbon Five Boutique softwareconsulting 20 people, all developers Agile development, collaboration, transparency Mix of projects: start-ups / non-profits / enterprise History of Java, but now RoR + misc.
  • 4.
    Agenda JavaScript unittesting: how & why BDD, jQuery, plugins Demo TDD with a simple test Problems & lessons learned Q&A
  • 5.
    Why NOT JSUnit Testing? Can’t get started We don’t; nobody else does Patterns unclear-- what does it look like Too many tools (and arch.) to choose from ... lack of tool & infrastructure support Flakey/bad experiences
  • 6.
    Why JS UnitTesting? Tests => Confidence => Ambition Take on more challenging requirements More homogenous architecture (w/o Flash) Push logic into browser... scalability Refactor reliably
  • 7.
    JS Testing Universe Styles of Tests: xUnit: JSUnit, QUnit, YUITest, etc. BDD: JSSpec, ScrewUnit Automation: In-browser w/ Selenium, WebDriver, JSTestDriver Out of browser: Env.js + Rhino
  • 8.
    Blue Ridge Ruby onRails plugin (therefore conventions!) Integrates ScrewUnit (BDD) and Smoke (mocking) Glue code and scripts Rhino & Env.js to run out of browser from Larry Karnowski, Runcoderun
  • 9.
    BDD Language closer touser stories Popularized by RSpec in Ruby describe -- organize before / after blocks Nesting describes -- to DRY One assert should per test example
  • 10.
    describe Stack do before(:each) do @stack = Stack.new @stack.push :item end describe "#peek" do it "should return the top element" do @stack.peek.should == :item end it "should not remove the top element" do @stack.peek @stack.size.should == 1 end end describe "#pop" do Stack (generated docs) it "should return the top element" do #peek @stack.pop.should == :item - should return the top element end it "should remove the top element" do - should not remove the top @stack.pop element @stack.size.should == 0 #pop end - should return the top element end end - should remove the top element
  • 11.
    BDD in RSpec describe'a man' { before { @man = Man.new :luck=>5 } describe '#decrement_luck' { it "decrements the luck field by the given amount" { @man.decrement_luck(3) @man.luck.should == 2 } } ... });
  • 12.
    BDD in ScrewUnit describe('Man',function() { var man; before(function() { man = new Man({luck: 5}); }); describe('#decrement_luck', function() { it("decrements the luck field by the given amount", function() { man.decrement_luck(3); expect(man.luck()).to(equal, 2) }); }); ... });
  • 13.
  • 14.
    Wizard-izer (our example) Given a form comprised of fieldsets Creates a “panel” for each fieldset Hides all but the first panel Adds buttons to navigate panels etc.
  • 15.
    Blue Ridge Installation http://github.com/relevance/blue-ridge/tree/master # Within a rails project > ./script/plugin install git:// github.com/relevance/blueridge.git > ./script/generate blue_ridge > ./script/generate javascript_spec wiz
  • 16.
    Maven Installation http://code.google.com/p/javascript-test-maven-plugin/ <plugin> <executions><execution> <goals> <goal>javascript-test</goal> </goals> </execution></executions> <groupId>com.carbonfive.javascript-test</groupId> <artifactId>javascript-test-maven-plugin</artifactId> <version>1.0-beta1</version> <configuration> <includes> <include>src/test/javascript/suite*.html</include> </includes> </configuration> </plugin>
  • 17.
    3 Components Fixture -markup that JS requires test/javascript/widget.html Spec - tests test/javascript/specs/widget_spec.js) Code to test - independent JS <public>/javascript/widget.js
  • 18.
    jQuery Functional DOM manipulation Onefunction $: $(selector) eg. $(‘p.cls’) Chainable -- $ (‘p.cls’).addClass(‘abc’).removeClass(‘cls’).etc(...) Plugins
  • 19.
    jQuery Plugin Add functionto jQuery.fn.reverse() = function() { jQuery object // “this” is list of nodes Receive list of nodes $(this).each(function() { var t = $(this).text(); Do your stuff $(this).text(t); } Return “this” return this; Good tutorials online }
  • 20.
    DOM cleanup Tools don’treset your DOM automatically They are writing status back to the page (yuck!) Solution Wrap fixtures (in #fixture container) before function that restores DOM state
  • 21.
    DOM cleanup fn //before(function() { $('#fixture).fixture(); }); jQuery.fn.fixture = function() { this.each(function() { if (this.original) { $(this).html(this.original); } else { this.original = $(this).html(); } }); }
  • 22.
    Put CSS inFixture Helps with debugging Use FireBug to inspect
  • 23.
    Structure as ajQuery plugin jQuery is not required-- but really helps you organize code Separate document.ready() from code-- dependency injection Use as organizing principal, and dependency injection Write modular code, instead page-specific code
  • 24.
  • 25.
    Not Hard toMake JS Testing a Part of Your Process
  • 26.
    Markup Design Generated inJS? On the server? Hybrid? Unit testing encourages generating more markup in JavaScript Will affect SEO and accessibility (good or bad)
  • 27.
    When to test SomeJS can be experimental Test-drive when you have a good sense of what he component is Testing is a refactoring tool As code gets complicated, pull into testable units
  • 28.
    Don’t Be Afraidof JS Reminder wizard Method chooser demo-- data driven JS, with ajax structure
  • 29.
    What to UnitTest / Limitations AJAX JavaScript Unit Testing setTimeout() setInterval() DOM Business Manipulation Logic Animations Computed sizes
  • 30.
    It’s Not BrowserTesting ...it’s JS unit testing Not cross-browser testing Not your production markup Functional test framework still have a place
  • 31.
    Our Testing Stack Selenium Integration & Browser Testing 1/10th Controller/View Integration Tests 1/5th Javascript Unit Testing Model Unit Controller Unit 1/10th Tests Tests
  • 32.
    JavaScript Testing Yes WeCan! Andy Peterson Jonah Williams andy@carbonfive.com jonah@carbonfive.com skype: ndpsoft
  • 33.
    Blue Ridge TODOs Runin more browsers (not just Firefox) Speed Events Driving in multiple browsers Using code served from app Shenandoah
  • 34.
  • 35.
    References (Bibliography) http://github.com/relevance/blue-ridge http://pivotallabs.com/users/nick/blog/articles/455- better-javascript-testing-through-screwunit http://github.com/ndp/wizardize http://www.slideshare.net/jeresig/understanding- javascript-testing http://code.google.com/p/javascript-test-maven- plugin/

Editor's Notes

  • #2 Ask everyone to introduce themselves quickly.
  • #3 Who Writes Java? Write Unit tests? Who Writes Ruby Who Writes JavaScript? Do you write unit tests? BDD? jQuery?
  • #6 John Resig Survey: http://www.slideshare.net/jeresig/understanding-javascript-testing Counter each of these!!!!
  • #7 we wrote something we otherwise would have outsourced to Flash developers-- demo method explorer
  • #10 # nested describes--- # need button to go to next fieldset it(&quot;should add &apos;next&apos; button to first fieldset&quot;, function() { expect($(&quot;form fieldset:first a&quot;).text()).to(equal, &apos;Next&apos;); }); it(&quot;should add &apos;next&apos; class to next button&quot;, function() { expect($(&quot;form fieldset:first a&quot;).hasClass(&apos;next&apos;)).to(equal, true); }); it(&quot;should add &apos;next&apos; button to second fieldset&quot;, function() { expect($(&quot;form fieldset:nth(1) a:last&quot;).text()).to(equal, &apos;Next&apos;); }); it(&quot;should not add &apos;next&apos; buttons to last fieldset&quot;, function() { expect($(&quot;form fieldset:last a&quot;).text()).to(equal, &apos;&apos;); }); # code $(&apos;fieldset:not(:last)&apos;).each(function() { $(this).append(&apos;&lt;a class=&quot;next&quot;&gt;Next&lt;/a&gt;&apos;) });
  • #15 ./script/generate javascript_spec wiz # create fixture &lt;form action=&quot;application.html&quot;&gt; &lt;fieldset title=&quot;A&quot;&gt;&lt;input type=&quot;text&quot; value=&quot;a&quot;/&gt;&lt;/fieldset&gt; &lt;fieldset title=&quot;B&quot;&gt;&lt;input type=&quot;text&quot; value=&quot;b&quot;/&gt;&lt;/fieldset&gt; &lt;fieldset title=&quot;C&quot;&gt;&lt;input type=&quot;text&quot; value=&quot;c&quot;/&gt;&lt;/fieldset&gt; &lt;/form&gt; # create simple test describe(&apos;initialization&apos;, function() { it(&quot;shows the first fieldset&quot;, function(){ expect($(&quot;form fieldset:first&quot;).is(&apos;:visible&apos;)).to(equal, true); }); }); # run from command line # tests that fail it(&quot;hides the second and third fieldset&quot;, function(){ expect($(&quot;form fieldset:not(:first)&quot;).is(&apos;:visible&apos;)).to(equal, false); }); # run from command line # write code in before block var wizardize = function() { $(&apos;form&apos;).each(function() { $(&apos;fieldset&apos;, this).hide(); $(&apos;fieldset:first&apos;, this).show(); });
  • #16 mkdir demo cd demo rails demo cd demo ./script/plugin install git://github.com/relevance/blue-ridge.git ./script/generate blue_ridge
  • #17 mkdir demo cd demo rails demo cd demo ./script/plugin install git://github.com/relevance/blue-ridge.git ./script/generate blue_ridge