SlideShare une entreprise Scribd logo
1  sur  59
Testing Client-Side
       Code
       with CoffeeScript and Jasmine




Brian P. Hogan                                      http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com           Rate this talk at http://spkr8.com/t/8682
about me


                          I write books

                          I build things

                          I teach people




Brian P. Hogan                       http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Why should we test
                    client-side code?



Brian P. Hogan                       http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
simple code is not
                          simple.

                     <img src="picture1.png" data-large-image="picture1-large.png">
                     <img src="picture2.png" data-large-image="picture2-large.png">
                     <img src="picture3.png" data-large-image="picture3-large.png">




Brian P. Hogan                                                                 http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
simple code is not
                          simple.
                     $(function(){
                        if($(window).width() > 480){
                          var images = $("img");
                          images.each(function(index){
                            $(this).attr("src", $(this).attr("data-large-image"));
                          });
                        }
                     })




Brian P. Hogan                                                                       http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Lots of logic
               Detecting the screen size

               testing that each image got changed

               checking that it only happens on
               large screens




Brian P. Hogan                                http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
But client-side
             testing can be hard!



Brian P. Hogan                 http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Challenges
               The DOM

               Tightly-coupled code

               Callbacks

               We don’t know how to test




Brian P. Hogan                             http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
“Laws of Testing”
               You may not write production code
               until you have written a failing unit
               test.




Brian P. Hogan                                http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
“Laws of Testing”
              You may not write more of a unit test
              than is sufficient to fail, and not
              compiling is failing




Brian P. Hogan                                http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
“Laws of Testing”
               You may not write more production
               code than is sufficient to pass the
               currently failing test




Brian P. Hogan                                http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
But what if I don’t
             know how to test the
                   feature?


Brian P. Hogan                 http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Spike!



Brian P. Hogan               http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
You may not write production code
               until you have written a failing unit
               test.




Brian P. Hogan                                http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Writing Testable Code



Brian P. Hogan              http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Use objects!
                     encapsulation!




Brian P. Hogan                        http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
object literals



Brian P. Hogan                        http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
object literal
                      var MyObject = {
                         sayHello: function(){
                            alert("Hello!");
                         },
                         sayGoodbye: function(){
                            alert("Goodbye");
                         }
                      };

                      MyObject.sayHello();
                      MyObject.sayGoodbye();




Brian P. Hogan                                     http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Remember this?

                    $(function(){
                       if($(window).width() > 480){
                         var images = $("img");
                         images.each(function(index){
                           $(this).attr("src", $(this).attr("data-large-image"));
                         });
                       }
                    })




Brian P. Hogan                                                                      http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Isn’t this cleaner?


                    $(function(){
                       ImageReplacer.replaceAll();
                    })




Brian P. Hogan                                       http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
But how do we test?



Brian P. Hogan                  http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Lots of options
               QUnit

               Mocha

               Jasmine

               ... lots more




Brian P. Hogan                        http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Jasmine



Brian P. Hogan                http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
A Jasmine Test

                    describe("JavaScript's Math works!", function() {
                      it("adds numbers properly", function() {
                        var result = 5 + 5;
                        expect(result).toEqual(10);
                      });
                    });




Brian P. Hogan                                                          http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Describe

                    describe("JavaScript's Math works!", function() {
                      it("adds numbers properly", function() {
                        var result = 5 + 5;
                        expect(result).toEqual(10);
                      });
                    });




Brian P. Hogan                                                          http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
it()

                    describe("JavaScript's Math works!", function() {
                      it("adds numbers properly", function() {
                        var result = 5 + 5;
                        expect(result).toEqual(10);
                      });
                    });




Brian P. Hogan                                                          http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
expect()

                    describe("JavaScript's Math works!", function() {
                      it("adds numbers properly", function() {
                        var result = 5 + 5;
                        expect(result).toEqual(10);
                      });
                    });




Brian P. Hogan                                                          http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Jasmine Test

               describe("The ImageReplacer object", function() {
                 it("replaces picture1 with picture1-large", function() {
                   ImageReplacer.replaceAll();
                   expect($("#picture1").attr("src")).toEqual("picture1-large.jpg");
                 });
               });




Brian P. Hogan                                                                         http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Jasmine Test

               describe("The ImageReplacer object", function() {
                 it("replaces picture1 with picture1-large", function() {
                   ImageReplacer.replaceAll();
                   expect($("#picture1").attr("src")).toEqual("picture1-large.jpg");
                 });
               });




Brian P. Hogan                                                                         http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
CoffeeScript



Brian P. Hogan                     http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Jasmine Test

               describe("The ImageReplacer object", function() {
                 it("replaces picture1 with picture1-large", function() {
                   ImageReplacer.replaceAll();
                   expect($("#picture1").attr("src")).toEqual("picture1-large.jpg");
                 });
               });




Brian P. Hogan                                                                         http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Into this.

               describe "JavaScript's Math works!", ->
                 it "adds numbers properly", ->
                   result = 5 + 5
                   expect(result).toEqual 10




Brian P. Hogan                                           http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
CoffeeScript helps us
              write better
               JavaScript.


Brian P. Hogan              http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
CoffeeScript uses
        significant whitespace
              for scope.



Brian P. Hogan             http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
CoffeeScript borrows
             from Python and Ruby
                   syntax.



Brian P. Hogan                 http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
CoffeeScript compiles
          to regular, plain old
               JavaScript.



Brian P. Hogan              http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Declaring variables
                   and objects


                                           var box, colors, firstName, lastName;

       firstName = "Homer"                 firstName = "Homer";
       lastName = "Simpson"                lastName = "Simpson";

       colors = ["Green", "Blue", "Red"]   colors = ["Green", "Blue", "Red"];

       box =                               box = {
         height: 40                           height: 40,
         width: 60                            width: 60,
         color: red                           color: red
                                           };




Brian P. Hogan                                                       http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
CoffeeScript uses function expressions.


        hello (name) ->
          alert "Hello #{name}"




        var hello = function(name){
          alert("Hello " + name + "!");
        }




Brian P. Hogan                            http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
-> means
                    function(){}



Brian P. Hogan                     http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Not bad...

               describe("The ImageReplacer object", function() {
                 it("replaces picture1 with picture1-large", function() {
                   ImageReplacer.replaceAll();
                   expect($("#picture1").attr("src")).toEqual("picture1-large.jpg");
                 });
               });




Brian P. Hogan                                                                         http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Much clearer.

               describe "JavaScript's Math works!", ->
                 it "adds numbers properly", ->
                   result = 5 + 5
                   expect(result).toEqual 10




Brian P. Hogan                                           http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
CoffeeScript wraps
              everything in
          (function(){ ... })()


Brian P. Hogan              http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
CoffeeScript
                     Advantages
               Everything wrapped in an anonymous
               function

               Less noise

               Helps enforce good JS Style

               Makes tests easier to read


Brian P. Hogan                                http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
cake



Brian P. Hogan             http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
{spawn, exec} = require 'child_process'

             task 'watch', 'continually build with --watch', ->

                src = spawn 'coffee', ['-cw', 'src']
                src.stdout.on 'data', (data) -> console.log data.toString().trim()

                spec = spawn 'coffee', ['-cw', 'spec']
                spec.stdout.on 'data', (data) -> console.log data.toString().trim()




Brian P. Hogan                                                                http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Project Structure
               Project/

                    src/

                      ImageReplacer.coffee

                    spec/

                      ImageReplacer_spec.coffee

                    Cakefile
Brian P. Hogan                                    http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Jasmine-Standalone
      https://github.com/pivotal/jasmine/
                   downloads




Brian P. Hogan                       http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Demo
               Write our ImageReplacer with
               CoffeeScript, test-first.

                    Replace an image

                    Replace all images

                    Ensure mobile only




Brian P. Hogan                                http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Requirements

               Mobile sites (< 900 px wide) load
               small images

               Other sizes (> 900px wide) load
               larger images

               Only images with large images should
               change


Brian P. Hogan                                   http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Demo



Brian P. Hogan             http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Async code?
                     Use spies!



Brian P. Hogan                    http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
Decouple things!



Brian P. Hogan                         http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
describe "Searching Twitter", ->

                it "creates an AJAX request to Twitter with the keyword 'JavaScript'", ->
                  expectedURL = "http://search.twitter.com/search.json?q=javascript"
                  twitter = new Twitter()
                  spy = spyOn($, "ajax")

                    twitter.search "javascript"

                    generatedURL = spy.mostRecentCall.args[0].url

                    expect(generatedURL).toEqual expectedURL




Brian P. Hogan                                                                      http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
describe "Searching Twitter", ->

                it "creates an AJAX request to Twitter with the keyword 'JavaScript'", ->
                  expectedURL = "http://search.twitter.com/search.json?q=javascript"
                  twitter = new Twitter()
                  spy = spyOn($, "ajax")

                    twitter.search "javascript"

                    generatedURL = spy.mostRecentCall.args[0].url

                    expect(generatedURL).toEqual expectedURL




Brian P. Hogan                                                                      http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
describe "Searching Twitter", ->

                it "creates an AJAX request to Twitter with the keyword 'JavaScript'", ->
                  expectedURL = "http://search.twitter.com/search.json?q=javascript"
                  twitter = new Twitter()
                  spy = spyOn($, "ajax")

                    twitter.search "javascript"

                    generatedURL = spy.mostRecentCall.args[0].url

                    expect(generatedURL).toEqual expectedURL




Brian P. Hogan                                                                      http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
describe "Searching Twitter", ->

                it "creates an AJAX request to Twitter with the keyword 'JavaScript'", ->
                  expectedURL = "http://search.twitter.com/search.json?q=javascript"
                  twitter = new Twitter()
                  spy = spyOn($, "ajax")

                    twitter.search "javascript"

                    generatedURL = spy.mostRecentCall.args[0].url

                    expect(generatedURL).toEqual expectedURL




Brian P. Hogan                                                                      http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
What we learned
               Putting things in objects makes them
               more testable

               CoffeeScript and Jasmine make tests
               easier to read and write

               Client-side code can be testable just
               like server-side code



Brian P. Hogan                                http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
What next?
               Explore Phantom.js for headless
               testing

               Integrate client-side testing into a
               continuous integration server

               Investigate other ways to create
               objects



Brian P. Hogan                                   http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com
http://spkr8.com/t/16851




Brian P. Hogan                             http://spkr8.com/t/16851
twitter: @bphogan
www.bphogan.com

Contenu connexe

Plus de Brian Hogan

Creating and Deploying Static Sites with Hugo
Creating and Deploying Static Sites with HugoCreating and Deploying Static Sites with Hugo
Creating and Deploying Static Sites with HugoBrian Hogan
 
Automating the Cloud with Terraform, and Ansible
Automating the Cloud with Terraform, and AnsibleAutomating the Cloud with Terraform, and Ansible
Automating the Cloud with Terraform, and AnsibleBrian Hogan
 
Create Development and Production Environments with Vagrant
Create Development and Production Environments with VagrantCreate Development and Production Environments with Vagrant
Create Development and Production Environments with VagrantBrian Hogan
 
Getting Started Contributing To Open Source
Getting Started Contributing To Open SourceGetting Started Contributing To Open Source
Getting Started Contributing To Open SourceBrian Hogan
 
Rethink Frontend Development With Elm
Rethink Frontend Development With ElmRethink Frontend Development With Elm
Rethink Frontend Development With ElmBrian Hogan
 
FUD-Free Accessibility for Web Developers - Also, Cake.
FUD-Free Accessibility for Web Developers - Also, Cake.FUD-Free Accessibility for Web Developers - Also, Cake.
FUD-Free Accessibility for Web Developers - Also, Cake.Brian Hogan
 
Building A Gem From Scratch
Building A Gem From ScratchBuilding A Gem From Scratch
Building A Gem From ScratchBrian Hogan
 
Intro To Advanced Ruby
Intro To Advanced RubyIntro To Advanced Ruby
Intro To Advanced RubyBrian Hogan
 
Turning Passion Into Words
Turning Passion Into WordsTurning Passion Into Words
Turning Passion Into WordsBrian Hogan
 
HTML5 and CSS3 Today
HTML5 and CSS3 TodayHTML5 and CSS3 Today
HTML5 and CSS3 TodayBrian Hogan
 
Web Development With Ruby - From Simple To Complex
Web Development With Ruby - From Simple To ComplexWeb Development With Ruby - From Simple To Complex
Web Development With Ruby - From Simple To ComplexBrian Hogan
 
Stop Reinventing The Wheel - The Ruby Standard Library
Stop Reinventing The Wheel - The Ruby Standard LibraryStop Reinventing The Wheel - The Ruby Standard Library
Stop Reinventing The Wheel - The Ruby Standard LibraryBrian Hogan
 
Intro to Ruby - Twin Cities Code Camp 7
Intro to Ruby - Twin Cities Code Camp 7Intro to Ruby - Twin Cities Code Camp 7
Intro to Ruby - Twin Cities Code Camp 7Brian Hogan
 
Make GUI Apps with Shoes
Make GUI Apps with ShoesMake GUI Apps with Shoes
Make GUI Apps with ShoesBrian Hogan
 
Story-driven Testing
Story-driven TestingStory-driven Testing
Story-driven TestingBrian Hogan
 
Learning To Walk In Shoes
Learning To Walk In ShoesLearning To Walk In Shoes
Learning To Walk In ShoesBrian Hogan
 
Rails and Legacy Databases - RailsConf 2009
Rails and Legacy Databases - RailsConf 2009Rails and Legacy Databases - RailsConf 2009
Rails and Legacy Databases - RailsConf 2009Brian Hogan
 

Plus de Brian Hogan (20)

Creating and Deploying Static Sites with Hugo
Creating and Deploying Static Sites with HugoCreating and Deploying Static Sites with Hugo
Creating and Deploying Static Sites with Hugo
 
Automating the Cloud with Terraform, and Ansible
Automating the Cloud with Terraform, and AnsibleAutomating the Cloud with Terraform, and Ansible
Automating the Cloud with Terraform, and Ansible
 
Create Development and Production Environments with Vagrant
Create Development and Production Environments with VagrantCreate Development and Production Environments with Vagrant
Create Development and Production Environments with Vagrant
 
Docker
DockerDocker
Docker
 
Getting Started Contributing To Open Source
Getting Started Contributing To Open SourceGetting Started Contributing To Open Source
Getting Started Contributing To Open Source
 
Rethink Frontend Development With Elm
Rethink Frontend Development With ElmRethink Frontend Development With Elm
Rethink Frontend Development With Elm
 
FUD-Free Accessibility for Web Developers - Also, Cake.
FUD-Free Accessibility for Web Developers - Also, Cake.FUD-Free Accessibility for Web Developers - Also, Cake.
FUD-Free Accessibility for Web Developers - Also, Cake.
 
Building A Gem From Scratch
Building A Gem From ScratchBuilding A Gem From Scratch
Building A Gem From Scratch
 
Intro To Advanced Ruby
Intro To Advanced RubyIntro To Advanced Ruby
Intro To Advanced Ruby
 
Turning Passion Into Words
Turning Passion Into WordsTurning Passion Into Words
Turning Passion Into Words
 
HTML5 and CSS3 Today
HTML5 and CSS3 TodayHTML5 and CSS3 Today
HTML5 and CSS3 Today
 
Web Development With Ruby - From Simple To Complex
Web Development With Ruby - From Simple To ComplexWeb Development With Ruby - From Simple To Complex
Web Development With Ruby - From Simple To Complex
 
Stop Reinventing The Wheel - The Ruby Standard Library
Stop Reinventing The Wheel - The Ruby Standard LibraryStop Reinventing The Wheel - The Ruby Standard Library
Stop Reinventing The Wheel - The Ruby Standard Library
 
Intro to Ruby
Intro to RubyIntro to Ruby
Intro to Ruby
 
Intro to Ruby - Twin Cities Code Camp 7
Intro to Ruby - Twin Cities Code Camp 7Intro to Ruby - Twin Cities Code Camp 7
Intro to Ruby - Twin Cities Code Camp 7
 
Make GUI Apps with Shoes
Make GUI Apps with ShoesMake GUI Apps with Shoes
Make GUI Apps with Shoes
 
The Why Of Ruby
The Why Of RubyThe Why Of Ruby
The Why Of Ruby
 
Story-driven Testing
Story-driven TestingStory-driven Testing
Story-driven Testing
 
Learning To Walk In Shoes
Learning To Walk In ShoesLearning To Walk In Shoes
Learning To Walk In Shoes
 
Rails and Legacy Databases - RailsConf 2009
Rails and Legacy Databases - RailsConf 2009Rails and Legacy Databases - RailsConf 2009
Rails and Legacy Databases - RailsConf 2009
 

Testing Client-side Code with Jasmine and CoffeeScript

  • 1. Testing Client-Side Code with CoffeeScript and Jasmine Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com Rate this talk at http://spkr8.com/t/8682
  • 2. about me I write books I build things I teach people Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 3. Why should we test client-side code? Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 4. simple code is not simple. <img src="picture1.png" data-large-image="picture1-large.png"> <img src="picture2.png" data-large-image="picture2-large.png"> <img src="picture3.png" data-large-image="picture3-large.png"> Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 5. simple code is not simple. $(function(){ if($(window).width() > 480){ var images = $("img"); images.each(function(index){ $(this).attr("src", $(this).attr("data-large-image")); }); } }) Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 6. Lots of logic Detecting the screen size testing that each image got changed checking that it only happens on large screens Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 7. But client-side testing can be hard! Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 8. Challenges The DOM Tightly-coupled code Callbacks We don’t know how to test Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 9. “Laws of Testing” You may not write production code until you have written a failing unit test. Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 10. “Laws of Testing” You may not write more of a unit test than is sufficient to fail, and not compiling is failing Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 11. “Laws of Testing” You may not write more production code than is sufficient to pass the currently failing test Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 12. But what if I don’t know how to test the feature? Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 13. Spike! Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 14. You may not write production code until you have written a failing unit test. Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 15. Writing Testable Code Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 16. Use objects! encapsulation! Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 17. object literals Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 18. object literal var MyObject = { sayHello: function(){ alert("Hello!"); }, sayGoodbye: function(){ alert("Goodbye"); } }; MyObject.sayHello(); MyObject.sayGoodbye(); Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 19. Remember this? $(function(){ if($(window).width() > 480){ var images = $("img"); images.each(function(index){ $(this).attr("src", $(this).attr("data-large-image")); }); } }) Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 20. Isn’t this cleaner? $(function(){ ImageReplacer.replaceAll(); }) Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 21. But how do we test? Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 22. Lots of options QUnit Mocha Jasmine ... lots more Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 23. Jasmine Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 24. A Jasmine Test describe("JavaScript's Math works!", function() { it("adds numbers properly", function() { var result = 5 + 5; expect(result).toEqual(10); }); }); Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 25. Describe describe("JavaScript's Math works!", function() { it("adds numbers properly", function() { var result = 5 + 5; expect(result).toEqual(10); }); }); Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 26. it() describe("JavaScript's Math works!", function() { it("adds numbers properly", function() { var result = 5 + 5; expect(result).toEqual(10); }); }); Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 27. expect() describe("JavaScript's Math works!", function() { it("adds numbers properly", function() { var result = 5 + 5; expect(result).toEqual(10); }); }); Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 28. Jasmine Test describe("The ImageReplacer object", function() { it("replaces picture1 with picture1-large", function() { ImageReplacer.replaceAll(); expect($("#picture1").attr("src")).toEqual("picture1-large.jpg"); }); }); Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 29. Jasmine Test describe("The ImageReplacer object", function() { it("replaces picture1 with picture1-large", function() { ImageReplacer.replaceAll(); expect($("#picture1").attr("src")).toEqual("picture1-large.jpg"); }); }); Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 30. CoffeeScript Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 31. Jasmine Test describe("The ImageReplacer object", function() { it("replaces picture1 with picture1-large", function() { ImageReplacer.replaceAll(); expect($("#picture1").attr("src")).toEqual("picture1-large.jpg"); }); }); Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 32. Into this. describe "JavaScript's Math works!", -> it "adds numbers properly", -> result = 5 + 5 expect(result).toEqual 10 Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 33. CoffeeScript helps us write better JavaScript. Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 34. CoffeeScript uses significant whitespace for scope. Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 35. CoffeeScript borrows from Python and Ruby syntax. Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 36. CoffeeScript compiles to regular, plain old JavaScript. Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 37. Declaring variables and objects var box, colors, firstName, lastName; firstName = "Homer" firstName = "Homer"; lastName = "Simpson" lastName = "Simpson"; colors = ["Green", "Blue", "Red"] colors = ["Green", "Blue", "Red"]; box = box = { height: 40 height: 40, width: 60 width: 60, color: red color: red }; Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 38. CoffeeScript uses function expressions. hello (name) -> alert "Hello #{name}" var hello = function(name){ alert("Hello " + name + "!"); } Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 39. -> means function(){} Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 40. Not bad... describe("The ImageReplacer object", function() { it("replaces picture1 with picture1-large", function() { ImageReplacer.replaceAll(); expect($("#picture1").attr("src")).toEqual("picture1-large.jpg"); }); }); Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 41. Much clearer. describe "JavaScript's Math works!", -> it "adds numbers properly", -> result = 5 + 5 expect(result).toEqual 10 Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 42. CoffeeScript wraps everything in (function(){ ... })() Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 43. CoffeeScript Advantages Everything wrapped in an anonymous function Less noise Helps enforce good JS Style Makes tests easier to read Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 44. cake Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 45. {spawn, exec} = require 'child_process' task 'watch', 'continually build with --watch', -> src = spawn 'coffee', ['-cw', 'src'] src.stdout.on 'data', (data) -> console.log data.toString().trim() spec = spawn 'coffee', ['-cw', 'spec'] spec.stdout.on 'data', (data) -> console.log data.toString().trim() Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 46. Project Structure Project/ src/ ImageReplacer.coffee spec/ ImageReplacer_spec.coffee Cakefile Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 47. Jasmine-Standalone https://github.com/pivotal/jasmine/ downloads Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 48. Demo Write our ImageReplacer with CoffeeScript, test-first. Replace an image Replace all images Ensure mobile only Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 49. Requirements Mobile sites (< 900 px wide) load small images Other sizes (> 900px wide) load larger images Only images with large images should change Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 50. Demo Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 51. Async code? Use spies! Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 52. Decouple things! Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 53. describe "Searching Twitter", -> it "creates an AJAX request to Twitter with the keyword 'JavaScript'", -> expectedURL = "http://search.twitter.com/search.json?q=javascript" twitter = new Twitter() spy = spyOn($, "ajax") twitter.search "javascript" generatedURL = spy.mostRecentCall.args[0].url expect(generatedURL).toEqual expectedURL Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 54. describe "Searching Twitter", -> it "creates an AJAX request to Twitter with the keyword 'JavaScript'", -> expectedURL = "http://search.twitter.com/search.json?q=javascript" twitter = new Twitter() spy = spyOn($, "ajax") twitter.search "javascript" generatedURL = spy.mostRecentCall.args[0].url expect(generatedURL).toEqual expectedURL Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 55. describe "Searching Twitter", -> it "creates an AJAX request to Twitter with the keyword 'JavaScript'", -> expectedURL = "http://search.twitter.com/search.json?q=javascript" twitter = new Twitter() spy = spyOn($, "ajax") twitter.search "javascript" generatedURL = spy.mostRecentCall.args[0].url expect(generatedURL).toEqual expectedURL Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 56. describe "Searching Twitter", -> it "creates an AJAX request to Twitter with the keyword 'JavaScript'", -> expectedURL = "http://search.twitter.com/search.json?q=javascript" twitter = new Twitter() spy = spyOn($, "ajax") twitter.search "javascript" generatedURL = spy.mostRecentCall.args[0].url expect(generatedURL).toEqual expectedURL Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 57. What we learned Putting things in objects makes them more testable CoffeeScript and Jasmine make tests easier to read and write Client-side code can be testable just like server-side code Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 58. What next? Explore Phantom.js for headless testing Integrate client-side testing into a continuous integration server Investigate other ways to create objects Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com
  • 59. http://spkr8.com/t/16851 Brian P. Hogan http://spkr8.com/t/16851 twitter: @bphogan www.bphogan.com

Notes de l'éditeur

  1. \n
  2. \n
  3. Why should we test client side code?\n
  4. We have a web page that has images on the page. We want to think &amp;#x201C;mobile first&amp;#x201D; so we design for the small screen, using small images. We know we&amp;#x2019;ll have large screens looking at our pages so we&amp;#x2019;ll want to display large images too. So what we do is load the small images by default, and then load the large images using JavaScript. We place the sources to the large images in the custom data attributes of the images\n
  5. Here&amp;#x2019;s what the JavaScript for this might look like. We find all the image elements on the page with jQuery, then iterate over them and replace the attributes.\n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. So that&amp;#x2019;s all well and good for when you know what you&amp;#x2019;re doing, but what do you do when you are doing something new that you&amp;#x2019;ve never done before and don&amp;#x2019;t know how to test?\n
  13. You do a spike. You write some code and work out the details.\n
  14. See, the first law of testing says you may not write PRODUCTION code. So you should do spikes. You should experiment and get to know your problem domain. Then you should throw it all away and rewrite it test-driven.\n
  15. In order to write JavaScript code that&amp;#x2019;s testable, we need to stop writing nasty messes of jQuery code thrown about all over our page. We need to start thinking about objects.\n
  16. One of the keys to writing testable code is to start treating JavaScript as more than just a scripting language. It&amp;#x2019;s an object-oriented language and it provides many of the capabilities that we would take advantage of in our other languages. The biggest of those is encapsulation.\n
  17. There are lots of ways to define objects in JavaScript. There are some great books out there on the subject, but one of the most simple ways is to simply use an object literal. \n
  18. Here&amp;#x2019;s what the JavaScript for this might look like. We find all the image elements on the page with jQuery, then iterate over them and replace the attributes.\n
  19. Remember this code? So this is just jQuery code without any encapsulation whatsoever. It would be much nicer if we could do something like this:\n
  20. We can take all that messy code, put it in an object, and simply call that. Just by doing that alone, we&amp;#x2019;ve now made it a lot easier to test. \n
  21. But how do we actually do TDD in JavaScript? \n
  22. \n
  23. Jasmine is my testing framework of choice. It&amp;#x2019;s simple, easy to read, has great support, lots of nice plugins, and can be set to run in the browser or in an automated fashion.\n
  24. Jasmine tests are pretty simple.\n
  25. Describe describes a behavior we&amp;#x2019;re testing.\n
  26. It() is an actual test declaration\n
  27. Expect() runs some sort of check.\n
  28. Using Jasmine, we simply write a test case using Jasmine&amp;#x2019;s syntax that invokes our ImageReplacer object\n
  29. then do an expects statement that checks to see if something worked. Of course it&amp;#x2019;s a little more complicated than that.\n
  30. One other thing I&amp;#x2019;ve found incredibly useful for developing and testing this kind of code is CoffeeScript. \n
  31. With CoffeeScript&amp;#x2019;s syntax we can change this....\n
  32. into this. \n
  33. \n
  34. \n
  35. \n
  36. \n
  37. Variable declarations are all done by omitting the var keyword. CoffeeScript automatically adds this so we don&amp;#x2019;t accidentally get a variable in the wrong scope.\n
  38. \n
  39. The skinny arrow in CoffeeScript basically means the function declaration in JavaScript. It&amp;#x2019;s less typing and less parentheses and curly braces to match. \n
  40. That matters a lot when doing things like this....\n
  41. because we can make them look like this. It becomes much easier to read when doing nested functions.\n
  42. When CoffeeScript is compiled to JavaScript, the JavaScript is all wrapped in an anonymous function which makes it much harder to accidentally pollute the global namespace.\n
  43. \n
  44. Cake is a build language that comes with CoffeeScript when you install it with Node.js&amp;#x2019;s npm package manager. With cake we can set up a build system to automatically compile CoffeeScript files to JavaScript as we work.\n
  45. With this simple Cakefile in our project, we can easily start working with CoffeeScript.\n
  46. \n
  47. We can download and unzip the Jasmine Standalone download. This gives us everything we need to start doing some BDD work.\n
  48. \n
  49. \n
  50. \n
  51. Heavy use of spies and mock objects can make your test suite run fast and keep it decoupled. For example:\n
  52. The secret is to try to decouple things as much as possible.\n
  53. I have a Twitter object\n
  54. That Twitter object is going to do a jQuery AJAX call. I can spy on jQUery&amp;#x2019;s ajax function.\n
  55. When I call my Twitter object&amp;#x2019;s search method, it won&amp;#x2019;t actually call out to Twitter. But I can pretend it did and I can then check to ensure the URL my object constructed was correctly built from the search term. \n
  56. See? Here&amp;#x2019;s the assertion. When I created the spy I could have actually fed it a response that I could then parse out. So I can write these tests without ever hitting the server.\n
  57. \n
  58. \n
  59. \n