SlideShare uma empresa Scribd logo
1 de 156
Client-Side Unit Testing



                       Cloud Chen
                       2012/5/11
You don’t write tests
You know you should,
    but you don’t
You won’t be blamed for it
Because...
There are many common issues
   that prevent developers
       for writing tests
You think
You think
•   Tests are not necessary and irrelevant
You think
•   Tests are not necessary and irrelevant
•   Manual testing is enough
You think
•   Tests are not necessary and irrelevant
•   Manual testing is enough
•   Lacking of specification
You think
•   Tests are not necessary and irrelevant
•   Manual testing is enough
•   Lacking of specification
•   Spaghetti code are hard for testing
You think
•   Tests are not necessary and irrelevant
•   Manual testing is enough
•   Lacking of specification
•   Spaghetti code are hard for testing
•   Web application needs functional testing rather
    than unit testing
You think
•   Tests are not necessary and irrelevant
•   Manual testing is enough
•   Lacking of specification
•   Spaghetti code are hard for testing
•   Web application needs functional testing rather
    than unit testing
•   Unit testing looks like only useful for back-end
    code rather than front-end
You think
•   Tests are not necessary and irrelevant
•   Manual testing is enough
•   Lacking of specification
•   Spaghetti code are hard for testing
•   Web application needs functional testing rather
    than unit testing
•   Unit testing looks like only useful for back-end
    code rather than front-end
•   Lazy...
Our goal
Our goal

  Help you start
 writing unit tests
for front-end code
However,
Let’s start with another
     type of testing
Functional Testing
What is functional testing

 Functional testing is a type of black box
 testing that bases its test cases on the
 specifications of the software component
 under test. Functions are tested by feeding
 them input and examining the output, and
 internal program structure is rarely
 considered.
What is functional testing

 Functional testing is a type of black box
 testing that bases its test cases on the
 specifications of the software component
 under test. Functions are tested by feeding
 them input and examining the output, and
 internal program structure is rarely
 considered.
In a nutshell
In a nutshell

• Written from user perspective
In a nutshell

• Written from user perspective
• Proving users are able to reproduce defined steps
In a nutshell

• Written from user perspective
• Proving users are able to reproduce defined steps
• Don’t need to consider internal program structure
In a nutshell

• Written from user perspective
• Proving users are able to reproduce defined steps
• Don’t need to consider internal program structure
• Automating manual testing
Unit Testing
What is unit testing

unit testing is a method by which individual
units of source code, sets of one or more
computer program modules together with
associated control data, usage procedures, and
operating procedures, are tested to determine
if they are fit for use.
What is unit testing

unit testing is a method by which individual
units of source code, sets of one or more
computer program modules together with
associated control data, usage procedures, and
operating procedures, are tested to determine
if they are fit for use.
In a nutshell
In a nutshell
• Unit testing is complete isolation
In a nutshell
• Unit testing is complete isolation
• Must irrelevant to external dependencies
In a nutshell
• Unit testing is complete isolation
• Must irrelevant to external dependencies
• All unreliable or slow dependencies of a
  tested unit should be stubbed
In a nutshell
• Unit testing is complete isolation
• Must irrelevant to external dependencies
• All unreliable or slow dependencies of a
  tested unit should be stubbed
• Only the logic of that single unit is exercised
In a nutshell
• Unit testing is complete isolation
• Must irrelevant to external dependencies
• All unreliable or slow dependencies of a
  tested unit should be stubbed
• Only the logic of that single unit is exercised
• Must be fast
What are differences between
 functional and unit testing
What are differences between
 functional and unit testing
                    unit            functional


perspective     programmer              user

                every module        every process
   goal
              works as expected   works as expected

                  all cases
 coverage                          most user cases
               even edge cases

  result       programmer :)           user :)
What are differences between
 functional and unit testing
                    unit            functional


perspective     programmer              user

                every module        every process
   goal
              works as expected   works as expected

                  all cases
 coverage                          most user cases
               even edge cases

  result       programmer :)           user :)
What are differences between
 functional and unit testing
                    unit            functional


perspective     programmer              user

                every module        every process
   goal
              works as expected   works as expected

                  all cases
 coverage                          most user cases
               even edge cases

  result       programmer :)           user :)
What are differences between
 functional and unit testing
                    unit            functional


perspective     programmer              user

                every module        every process
   goal
              works as expected   works as expected

                  all cases
 coverage                          most user cases
               even edge cases

  result       programmer :)           user :)
What are differences between
 functional and unit testing
                    unit            functional


perspective     programmer              user

                every module        every process
   goal
              works as expected   works as expected

                  all cases
 coverage                          most user cases
               even edge cases

  result       programmer :)           user :)
Hence..
Hence..
•   We are programmer
Hence..
•   We are programmer
•   We care our code quality
Hence..
•   We are programmer
•   We care our code quality
•   We should write unit test code
Why, When
     of
unit testing
Why need unit testing
Why need unit testing

• Ensuring every component is bug-free
Why need unit testing

• Ensuring every component is bug-free
• Ensuring every component is easy to modify
Why need unit testing

• Ensuring every component is bug-free
• Ensuring every component is easy to modify
• Easy to locating bug of complex logic
Why need unit testing

• Ensuring every component is bug-free
• Ensuring every component is easy to modify
• Easy to locating bug of complex logic
• Preventing and capturing regression bug
Why need unit testing

• Ensuring every component is bug-free
• Ensuring every component is easy to modify
• Easy to locating bug of complex logic
• Preventing and capturing regression bug
• Testable code must be high readability and
  maintainability code
Why need unit testing

• Ensuring every component is bug-free
• Ensuring every component is easy to modify
• Easy to locating bug of complex logic
• Preventing and capturing regression bug
• Testable code must be high readability and
  maintainability code
• and so on...
When to do unit testing
When to do unit testing



• When you write your own classes, modules,
  libraries, frameworks.
When to do unit testing



• When you write your own classes, modules,
  libraries, frameworks.
• You don’t need to write test code for
  fundamental framework
testing style comparison
testing style comparison
                    TDD                    BDD


 based on      function oriented     feature oriented


   syntax      testing language          idiomatic


spring from      programmer          stakeholder || PO


accumulation     test as code      test as documentation
testing style comparison
                    TDD                    BDD


 based on      function oriented     feature oriented


   syntax      testing language          idiomatic


spring from      programmer          stakeholder || PO


accumulation     test as code      test as documentation
testing style comparison
                    TDD                    BDD


 based on      function oriented     feature oriented


   syntax      testing language          idiomatic


spring from      programmer          stakeholder || PO


accumulation     test as code      test as documentation
testing style comparison
                    TDD                    BDD


 based on      function oriented     feature oriented


   syntax      testing language          idiomatic


spring from      programmer          stakeholder || PO


accumulation     test as code      test as documentation
testing style comparison
                    TDD                    BDD


 based on      function oriented     feature oriented


   syntax      testing language          idiomatic


spring from      programmer          stakeholder || PO


accumulation     test as code      test as documentation
testing style comparison
testing style comparison


• It doesn’t matter which style you choose
testing style comparison


• It doesn’t matter which style you choose
• It does matter how many cases you have
testing style comparison
testing style comparison

 As we are using Scrum,
testing style comparison

 As we are using Scrum,
 BDD is more suitable for us.
BDD Framework
BDD Framework
•   Focuses on assertion, doesn’t depend on DOM
BDD Framework
•   Focuses on assertion, doesn’t depend on DOM
•   Succinct API
BDD Framework
•   Focuses on assertion, doesn’t depend on DOM
•   Succinct API
•   Natively support Spy
BDD Framework
•   Focuses on assertion, doesn’t depend on DOM
•   Succinct API
•   Natively support Spy
•   Support spec helper
BDD Framework
•   Focuses on assertion, doesn’t depend on DOM
•   Succinct API
•   Natively support Spy
•   Support spec helper
•   Be able to extend matcher
BDD Framework
•   Focuses on assertion, doesn’t depend on DOM
•   Succinct API
•   Natively support Spy
•   Support spec helper
•   Be able to extend matcher
•   Be able to run in several environments
BDD Framework
•   Focuses on assertion, doesn’t depend on DOM
•   Succinct API
•   Natively support Spy
•   Support spec helper
•   Be able to extend matcher
•   Be able to run in several environments
    1. Browser
BDD Framework
•   Focuses on assertion, doesn’t depend on DOM
•   Succinct API
•   Natively support Spy
•   Support spec helper
•   Be able to extend matcher
•   Be able to run in several environments
    1. Browser
    2. CI
BDD Framework
•   Focuses on assertion, doesn’t depend on DOM
•   Succinct API
•   Natively support Spy
•   Support spec helper
•   Be able to extend matcher
•   Be able to run in several environments
    1. Browser
    2. CI
    3. NodeJS
Typical Spec & Succinct API
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });
    afterEach(function () {
    });

    describe(“when initialized”, function() {
        beforeEach(function() {
            this.flashcard = new this.Flashcard({
                "Value": "here",
                "Translation": "这儿",
                    "Audio": "here_en.mp3",
                    "IsWord": true,
                    "ContentId": 174087,
                    "IsWordIKnow": false
              });
        });

        it(“composite key should be composed by ContentId and IsWord properties”,
function() {
             var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
             expect(this.flashcard.id).toEqual(expected_url);
        });
    });
Typical Spec & Succinct API
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');                 Suite
    });
    afterEach(function () {
    });

    describe(“when initialized”, function() {
        beforeEach(function() {
            this.flashcard = new this.Flashcard({
                "Value": "here",
                "Translation": "这儿",
                    "Audio": "here_en.mp3",
                    "IsWord": true,
                    "ContentId": 174087,
                    "IsWordIKnow": false
              });
        });

        it(“composite key should be composed by ContentId and IsWord properties”,
function() {
             var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
             expect(this.flashcard.id).toEqual(expected_url);
        });
    });
Typical Spec & Succinct API
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });
    afterEach(function () {                                    Inner Suite
    });

    describe(“when initialized”, function() {
        beforeEach(function() {
            this.flashcard = new this.Flashcard({
                "Value": "here",
                "Translation": "这儿",
                    "Audio": "here_en.mp3",
                    "IsWord": true,
                    "ContentId": 174087,
                    "IsWordIKnow": false
              });
        });

        it(“composite key should be composed by ContentId and IsWord properties”,
function() {
             var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
             expect(this.flashcard.id).toEqual(expected_url);
        });
    });
Typical Spec & Succinct API
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });
    afterEach(function () {
    });

    describe(“when initialized”, function() {
        beforeEach(function() {
            this.flashcard = new this.Flashcard({
                "Value": "here",
                "Translation": "这儿",
                    "Audio": "here_en.mp3",
                    "IsWord": true,
                    "ContentId": 174087,

              });
                    "IsWordIKnow": false
                                                                       Spec
        });

        it(“composite key should be composed by ContentId and IsWord properties”,
function() {
             var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
             expect(this.flashcard.id).toEqual(expected_url);
        });
    });
Typical Spec & Succinct API
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });
    afterEach(function () {
    });

    describe(“when initialized”, function() {
        beforeEach(function() {
            this.flashcard = new this.Flashcard({
                "Value": "here",
                "Translation": "这儿",
                    "Audio": "here_en.mp3",
                    "IsWord": true,
                    "ContentId": 174087,
                    "IsWordIKnow": false
              });
        });

        it(“composite key should be composed by ContentId and IsWord properties”,
function() {
             var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
             expect(this.flashcard.id).toEqual(expected_url);        Expectation
        });
    });
Typical Spec & Succinct API
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });
    afterEach(function () {
    });

    describe(“when initialized”, function() {
        beforeEach(function() {
            this.flashcard = new this.Flashcard({
                "Value": "here",
                "Translation": "这儿",
                    "Audio": "here_en.mp3",
                    "IsWord": true,
                    "ContentId": 174087,
                    "IsWordIKnow": false
              });
        });

        it(“composite key should be composed by ContentId and IsWord properties”,
function() {
             var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
             expect(this.flashcard.id).toEqual(expected_url);
        });
    });                                  Matcher
Typical Spec & Succinct API
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');         Setup
    });
    afterEach(function () {
    });

    describe(“when initialized”, function() {
        beforeEach(function() {
            this.flashcard = new this.Flashcard({
                "Value": "here",
                "Translation": "这儿",
                    "Audio": "here_en.mp3",
                    "IsWord": true,
                    "ContentId": 174087,
                    "IsWordIKnow": false
              });
        });

        it(“composite key should be composed by ContentId and IsWord properties”,
function() {
             var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
             expect(this.flashcard.id).toEqual(expected_url);
        });
    });
Typical Spec & Succinct API
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');         Setup
    });
    afterEach(function () {
    });                                                      Tear down
    describe(“when initialized”, function() {
        beforeEach(function() {
            this.flashcard = new this.Flashcard({
                "Value": "here",
                "Translation": "这儿",
                    "Audio": "here_en.mp3",
                    "IsWord": true,
                    "ContentId": 174087,
                    "IsWordIKnow": false
              });
        });

        it(“composite key should be composed by ContentId and IsWord properties”,
function() {
             var expected_url = this.flashcard.get('ContentId') + '-' +
this.flashcard.get('IsWord');
             expect(this.flashcard.id).toEqual(expected_url);
        });
    });
Other Native Matchers
expect(x).toEqual(y);
expect(x).toBe(y);
expect(x).toMatch(pattern);
expect(x).toBeDefined();
expect(x).toBeUndefined();
expect(x).toBeNull();
expect(x).toBeTruthy();
expect(x).toBeFalsy();
expect(x).toContain(y);
expect(x).toBeLessThan(y);
expect(x).toBeGreaterThan(y);
expect(function(){fn();}).toThrow(e);
Other Native Matchers
expect(x).not.toEqual(y);
expect(x).not.toBe(y);
expect(x).not.toMatch(pattern);
expect(x).not.toBeDefined();
expect(x).not.toBeUndefined();
expect(x).not.toBeNull();
expect(x).not.toBeTruthy();
expect(x).not.toBeFalsy();
expect(x).not.toContain(y);
expect(x).not.toBeLessThan(y);
expect(x).not.toBeGreaterThan(y);
expect(function(){fn();}).not.toThrow(e);
Spec Helper
beforeEach(function() {
  this.fixtures = {
      Flashcard: {
        valid: { // response starts here
          "Value": "here",
          "Translation": "这儿",
           "Audio": "here_en.mp3",
           "IsWord": true,
           "ContentId": 174087,
           "IsWordIKnow": false
         },
         error: { // response starts here
             "ErrorCode": "101",
             "IsSuccess": "false"
         }
     }
 }
Spec Helper
beforeEach(function() {
  this.fixtures = {
      Flashcard: {
        valid: { // response starts here
          "Value": "here",
          "Translation": "这儿",
            "Audio": "here_en.mp3",
            "IsWord": true,
            "ContentId": 174087,
            "IsWordIKnow": false
          },
          error: { // response starts here
              "ErrorCode": "101",
              "IsSuccess": "false"
          }
      }
 }

     Using this.fixtures.Flashcard.valid to access pre-defined fixture
     for testing when this spec file is included in your spec runner.
Spy
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when initialized”, function() {
          beforeEach(function() {
              spyOn(jQuery, ‘ajax’);
              this.flashcard = new this.Flashcard();
              this.flashcard.fetch();
          });

            it(“should communicate with back-end via http get method”, function() {
                expect(jQuery.ajax).toHaveBeenCalled();
                expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
            });
      });
});
Spy
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when initialized”, function() {
          beforeEach(function() {
              spyOn(jQuery, ‘ajax’);                       spy on instance method
              this.flashcard = new this.Flashcard();
              this.flashcard.fetch();
          });

            it(“should communicate with back-end via http get method”, function() {
                expect(jQuery.ajax).toHaveBeenCalled();
                expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
            });
      });
});
Spy
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when initialized”, function() {
          beforeEach(function() {
              spyOn(jQuery, ‘ajax’);
              this.flashcard = new this.Flashcard();
              this.flashcard.fetch();
          });                               original function won’t be called
            it(“should communicate with back-end via http get method”, function() {
                expect(jQuery.ajax).toHaveBeenCalled();
                expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
            });
      });
});
Spy
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when initialized”, function() {
          beforeEach(function() {
              spyOn(jQuery, ‘ajax’);
              this.flashcard = new this.Flashcard();
              this.flashcard.fetch();
          });

            it(“should communicate with back-end via http get method”, function() {
                expect(jQuery.ajax).toHaveBeenCalled();
                expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
            });
      });                                   match spied function was called
});
Spy
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when initialized”, function() {
          beforeEach(function() {
              spyOn(jQuery, ‘ajax’);
              this.flashcard = new this.Flashcard();
              this.flashcard.fetch();
          });

            it(“should communicate with back-end via http get method”, function() {
                expect(jQuery.ajax).toHaveBeenCalled();
                expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
            });
      });
});                         arguments of last call
Spy
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when initialized”, function() {
          beforeEach(function() {
              spyOn(jQuery, ‘ajax’);
              this.flashcard = new this.Flashcard();
              this.flashcard.fetch();
          });

            it(“should communicate with back-end via http get method”, function() {
                expect(jQuery.ajax).toHaveBeenCalled();
                expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’);
            });
      });
});                         arguments of last call      type of first argument
Extending Matcher
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
   expectation failed:
       Expected false to be truthy.
Extending Matcher
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
   expectation failed:
       Expected false to be truthy.




   jasmine-jquery comes to rescue
Matcher from
            jasmine jquery
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
   expectation failed:
       Expected false to be truthy.
Matcher from
            jasmine jquery
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
   expectation failed:
       Expected false to be truthy.

expect($(‘a’).toHaveClass(‘s-selected’);
Matcher from
            jasmine jquery
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
   expectation failed:
       Expected false to be truthy.

expect($(‘a’).toHaveClass(‘s-selected’);
   expectation failed:
Matcher from
            jasmine jquery
<a href=”#” class=”link”>this is a link</a>
expect($(‘a’).hasClass('s-selected')).toBeTruthy();
   expectation failed:
       Expected false to be truthy.

expect($(‘a’).toHaveClass(‘s-selected’);
   expectation failed:
       Expected '<a></a>' to have class 's-selected'.
jasmine-jquery features

• a set of custom matchers for jQuery
  framework
• an API for handling HTML fixtures in your
  specs
jasmine-jquery matchers
expect(x).toBeHidden()
expect(x).toBeVisible()
expect(x).toHaveAttr(attributeName, attributeValue)
expect(x).toHaveProp(propertyName, propertyValue)
expect(x).toHaveText(string)
expect(x).toHaveHtml(string)
expect(x).toHaveId(id)
expect(x).toBeDisabled()
expect(x).toBeFocused()
expect(x).toHandle(eventName)
jasmine-jquery fixtures
In myfixture.html file:

<div id="my-fixture">some complex content here</div>

Inside your test:

loadFixtures('myfixture.html');
$('#my-fixture').myTestedPlugin();
expect($('#my-fixture')).to...;
Running Jasmine
Standalone Runner




    * Manually manage of your project files and specs
Running Jasmine
Dedicated Server runs jasmine
Ruby jasmine Gem




     * Using yaml manages of your project files and specs
Recap rules of Unit Testing

• Unit testing is complete isolation
• Must irrelevant to external dependencies
• All unreliable or slow dependencies of a
  tested unit should be stubbed
• Only the logic of that single unit is exercised
• Must be fast
Recap rules of Unit Testing

• Unit testing is complete isolation
• Must irrelevant to external dependencies
• All unreliable or slow dependencies of a
  tested unit should be stubbed
• Only the logic of that single unit is exercised
• Must be fast
Stub every dependency
Stub every dependency

• Native spy feature of Jasmine is not enough
Stub every dependency

• Native spy feature of Jasmine is not enough
• It doesn’t support fake timer
Stub every dependency

• Native spy feature of Jasmine is not enough
• It doesn’t support fake timer
• It doesn’t support fake HTTP server
Stub every dependency

• Native spy feature of Jasmine is not enough
• It doesn’t support fake timer
• It doesn’t support fake HTTP server
• It misuses Spy and Stub
Stub every dependency

• Native spy feature of Jasmine is not enough
• It doesn’t support fake timer
• It doesn’t support fake HTTP server
• It misuses Spy and Stub

   Sinon.js comes to rescue
Spy/Stub/Mock
Spy/Stub/Mock
•   A test spy is a function that records arguments, return value,
    the value of this and exception thrown (if any) for all its calls.
    A test spy can be an anonymous function or it can wrap an
    existing function.
Spy/Stub/Mock
•   A test spy is a function that records arguments, return value,
    the value of this and exception thrown (if any) for all its calls.
    A test spy can be an anonymous function or it can wrap an
    existing function.

•   Test stubs are functions (spies) with pre-programmed
    behavior. They support the full test spy API in addition to
    methods which can be used to alter the stub's behavior.
Spy/Stub/Mock
•   A test spy is a function that records arguments, return value,
    the value of this and exception thrown (if any) for all its calls.
    A test spy can be an anonymous function or it can wrap an
    existing function.

•   Test stubs are functions (spies) with pre-programmed
    behavior. They support the full test spy API in addition to
    methods which can be used to alter the stub's behavior.

•   Mocks (and mock expectations) are fake methods (like spies)
    with pre-programmed behavior (like stubs) as well as pre-
    programmed expectations. A mock will fail your test if it is not
    used as expected.
Spy/Stub/Mock
Spy/Stub/Mock


 Spy
Spy/Stub/Mock


 Spy



       Stub
Spy/Stub/Mock


 Spy



       Stub


              Mock
Spy
describe("when initialized", function () {
    beforeEach(function() {
        sinon.spy(this, "Flashcards");           create a spy for this.Flashcards
    })

      afterEach(function() {
          this.Flashcards.restore();
      })

      it("should throw Error when cultureCode is not passed", function() {
          try {
              var flashcards = new this.Flashcards([], {
              });
          } catch(e) {}
          expect(this.Flashcards.calledOnce()).toBeTruthy();
          expect(this.Flashcards.threw()).toBeTruthy();
          expect(this.Flashcards.calledWith([], {})).toBeTruthy();
      });
});
Spy
describe("when initialized", function () {
    beforeEach(function() {
        sinon.spy(this, "Flashcards");
    })

      afterEach(function() {
          this.Flashcards.restore();                unwraps the spy
      })

      it("should throw Error when cultureCode is not passed", function() {
          try {
              var flashcards = new this.Flashcards([], {
              });
          } catch(e) {}
          expect(this.Flashcards.calledOnce()).toBeTruthy();
          expect(this.Flashcards.threw()).toBeTruthy();
          expect(this.Flashcards.calledWith([], {})).toBeTruthy();
      });
});
Spy
describe("when initialized", function () {
    beforeEach(function() {
        sinon.spy(this, "Flashcards");
    })

      afterEach(function() {
          this.Flashcards.restore();
      })

      it("should throw Error when cultureCode is not passed", function() {
          try {
              var flashcards = new this.Flashcards([], {
              });
          } catch(e) {}
          expect(this.Flashcards.calledOnce()).toBeTruthy();     spy was called once
          expect(this.Flashcards.threw()).toBeTruthy();
          expect(this.Flashcards.calledWith([], {})).toBeTruthy();
      });
});
Spy
describe("when initialized", function () {
    beforeEach(function() {
        sinon.spy(this, "Flashcards");
    })

      afterEach(function() {
          this.Flashcards.restore();
      })

      it("should throw Error when cultureCode is not passed", function() {
          try {
              var flashcards = new this.Flashcards([], {
              });
          } catch(e) {}
          expect(this.Flashcards.calledOnce()).toBeTruthy();
          expect(this.Flashcards.threw()).toBeTruthy();
          expect(this.Flashcards.calledWith([], {})).toBeTruthy();
      });
});
                                   spy threw exception at least once
Spy
describe("when initialized", function () {
    beforeEach(function() {
        sinon.spy(this, "Flashcards");
    })

      afterEach(function() {
          this.Flashcards.restore();
      })

      it("should throw Error when cultureCode is not passed", function() {
          try {
              var flashcards = new this.Flashcards([], {
              });
          } catch(e) {}
          expect(this.Flashcards.calledOnce()).toBeTruthy();
          expect(this.Flashcards.threw()).toBeTruthy();
          expect(this.Flashcards.calledWith([], {})).toBeTruthy();
      });
});                                    spy was called at least once with
                                             provided argument
Stub
describe("when update model", function () {
    beforeEach(function () {
        this.sync = sinon.stub(Backbone, "sync");
    });

      afterEach(function () {                     create a stub for this.Flashcards
          this.sync.restore();
      });

    it("should put IsWordIKnow and IsWord properties at the end of url property",
function () {
        this.flashcard.set('IsWordIKnow', true);

        var expected_url = this.flashcard.url() + '&IsWordIKnow=' +
this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord');

            this.flashcard.save();

            expect(this.sync.getCall(0).args[2].url).toEqual(expected_url);
      });
});
Stub
describe("when update model", function () {
    beforeEach(function () {
        this.sync = sinon.stub(Backbone, "sync");
    });

      afterEach(function () {
          this.sync.restore();
      });

    it("should put IsWordIKnow and IsWord properties at the end of url property",
function () {
        this.flashcard.set('IsWordIKnow', true);

        var expected_url = this.flashcard.url() + '&IsWordIKnow=' +
this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord');

            this.flashcard.save();

            expect(this.sync.getCall(0).args[2].url).toEqual(expected_url);
      });
});
                  save method will invoke Backbone.sync that already stubbed
Stub
describe("when update model", function () {
    beforeEach(function () {
        this.sync = sinon.stub(Backbone, "sync");
    });

      afterEach(function () {
          this.sync.restore();
      });

    it("should put IsWordIKnow and IsWord properties at the end of url property",
function () {
        this.flashcard.set('IsWordIKnow', true);

        var expected_url = this.flashcard.url() + '&IsWordIKnow=' +
this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord');

            this.flashcard.save();

            expect(this.sync.getCall(0).args[2].url).toEqual(expected_url);
      });
});
             original sync method won’t be called if it was stubbed
             So, no ajax request won’t be fired
Stub
describe("when update model", function () {
    beforeEach(function () {
        this.sync = sinon.stub(Backbone, "sync");
    });

      afterEach(function () {
          this.sync.restore();
      });

    it("should put IsWordIKnow and IsWord properties at the end of url property",
function () {
        this.flashcard.set('IsWordIKnow', true);

        var expected_url = this.flashcard.url() + '&IsWordIKnow=' +
this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord');

            this.flashcard.save();

            expect(this.sync.getCall(0).args[2].url).toEqual(expected_url);
      });
});

             get arguments of stubbed calling (spy can also do this)
Stub
              But, Spy cannot do like so:

it("should always confirm every confirmation", function () {
    sinon.stub(window, 'confirm');
    confirm.returns(true);
    expect(confirm('Are you sure?')).toBeTruthy();
    window.confirm.restore();
}

      Makes window.confirm() return truth
Stub
              But, Spy cannot do like so:

it("should always confirm every confirmation", function () {
    sinon.stub(window, 'confirm');
    confirm.returns(true);
    expect(confirm('Are you sure?')).toBeTruthy();
    window.confirm.restore();
}

      Native confirm behavior won’t fired
Stub
              But, Spy cannot do like so:

it("should always confirm every confirmation", function () {
    sinon.stub(window, 'confirm');
    confirm.returns(true);
    expect(confirm('Are you sure?')).toBeTruthy();
    window.confirm.restore();
}

      Native confirm behavior won’t fired




  It needs user interaction to finish this test without stub.
   This test probably be failed if user not confirms with it.
                  That test case is unstable.
Mock

• Mock focuses on implementation details of
  one method
• It utilizes upfront expectation to verify details
  rather than asserting after the details
Mock
describe("when initialized", function () {
    it("should throw Error when cultureCode is not passed", function() {
        var myAPI = { method: function () {} };

            var spy = sinon.spy();
            var mock = sinon.mock(myAPI);
            mock.expects("method").once();         expectation upfront
            myAPI.method();
            spy();
                                      verify mock behavior
            mock.verify();
            expect(spy.calledOnce).toBeTruthy();
      });
});
Without FakeTimers
it("should show teacher box after 1 hour", function () {
    var hour = 1000 * 60 * 60;
    setTimeout(showTeacherbox, hour);

      waits(hour);         must wait 1 hour...crazy
      runs(function() {
          expect($(‘#teacherbox’)).toBeVisible();
      });
});
FakeTimers
it("should show teacher box after 1 hour", function () {
    this.clock = sinon.useFakeTimers();
    var hour = 1000 * 60 * 60;
                                                create a fake timer
      setTimeout(showTeacherbox, hour);

      this.clock.tick(hour);
      expect($(‘#teacherbox’)).toBeVisible();

      this.clock.restore();
});
FakeTimers
it("should show teacher box after 1 hour", function () {
    this.clock = sinon.useFakeTimers();
    var hour = 1000 * 60 * 60;

      setTimeout(showTeacherbox, hour);
                                           tick the clock ahead 1 hour
      this.clock.tick(hour);
      expect($(‘#teacherbox’)).toBeVisible();

      this.clock.restore();
});
FakeTimers
it("should show teacher box after 1 hour", function () {
    this.clock = sinon.useFakeTimers();
    var hour = 1000 * 60 * 60;

      setTimeout(showTeacherbox, hour);

      this.clock.tick(hour - 1);
      expect($(‘#teacherbox’)).toBeHidden();    won’t happen

      this.clock.tick(1);
      expect($(‘#teacherbox’)).toBeVisible();    will happen

      this.clock.restore();
});
Fake Server
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when fetch”, function() {
          beforeEach(function() {
              this.server = sinon.fakeServer.create();
              this.flashcard = new this.Flashcard({contentId: 174087});
          });
          afterEach(function() {
               this.server.restore();
          });

            it(“should get data from backend”, function() {
               this.server.respondWith(
                   "GET",
                   "/community/dailylesson/wordiknowupdate.ashx?contentId=174087",
                   [200,
                    { "Content-Type": "application/json" },
                    '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true,
              "ContentId": 174087, "IsWordIKnow": false }'
                  ]
              );
               this.flashcard.fetch();
               this.server.respond();

                  expect(this.flashcard.get(‘Value’)).toEqual(‘here’);
            });
      });
});
Fake Server
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when fetch”, function() {
          beforeEach(function() {                               Fake server stub XHR
              this.server = sinon.fakeServer.create();
              this.flashcard = new this.Flashcard({contentId: 174087});
          });
          afterEach(function() {
               this.server.restore();
          });

            it(“should get data from backend”, function() {
               this.server.respondWith(
                   "GET",
                   "/community/dailylesson/wordiknowupdate.ashx?contentId=174087",
                   [200,
                    { "Content-Type": "application/json" },
                    '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true,
              "ContentId": 174087, "IsWordIKnow": false }'
                  ]
              );
               this.flashcard.fetch();
               this.server.respond();

                  expect(this.flashcard.get(‘Value’)).toEqual(‘here’);
            });
      });
});
Fake Server
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when fetch”, function() {
          beforeEach(function() {
              this.server = sinon.fakeServer.create();
              this.flashcard = new this.Flashcard({contentId: 174087});
          });
          afterEach(function() {
               this.server.restore();
          });                                          responds to given   URL and HTTP method
            it(“should get data from backend”, function() {
               this.server.respondWith(
                   "GET",
                   "/community/dailylesson/wordiknowupdate.ashx?contentId=174087",
                   [200,
                    { "Content-Type": "application/json" },
                    '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true,
              "ContentId": 174087, "IsWordIKnow": false }'
                  ]
              );
               this.flashcard.fetch();
               this.server.respond();

                  expect(this.flashcard.get(‘Value’)).toEqual(‘here’);
            });
      });
});
Fake Server
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when fetch”, function() {
          beforeEach(function() {
              this.server = sinon.fakeServer.create();
              this.flashcard = new this.Flashcard({contentId: 174087});
          });
          afterEach(function() {
               this.server.restore();
          });

            it(“should get data from backend”, function() {
               this.server.respondWith(
                                                                    given HTTP method
                   "GET",
                   "/community/dailylesson/wordiknowupdate.ashx?contentId=174087",
                   [200,
                    { "Content-Type": "application/json" },
                    '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true,
              "ContentId": 174087, "IsWordIKnow": false }'
                  ]
              );
               this.flashcard.fetch();
               this.server.respond();

                  expect(this.flashcard.get(‘Value’)).toEqual(‘here’);
            });
      });
});
Fake Server
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when fetch”, function() {
          beforeEach(function() {
              this.server = sinon.fakeServer.create();
              this.flashcard = new this.Flashcard({contentId: 174087});
          });
          afterEach(function() {
               this.server.restore();
          });

            it(“should get data from backend”, function() {
               this.server.respondWith(
                                                                          given URL
                   "GET",
                   "/community/dailylesson/wordiknowupdate.ashx?contentId=174087",
                   [200,
                    { "Content-Type": "application/json" },
                    '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true,
              "ContentId": 174087, "IsWordIKnow": false }'
                  ]
              );
               this.flashcard.fetch();
               this.server.respond();

                  expect(this.flashcard.get(‘Value’)).toEqual(‘here’);
            });
      });
});
Fake Server
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when fetch”, function() {
          beforeEach(function() {
              this.server = sinon.fakeServer.create();
              this.flashcard = new this.Flashcard({contentId: 174087});
          });
          afterEach(function() {
               this.server.restore();
          });

            it(“should get data from backend”, function() {
               this.server.respondWith(
                   "GET",
                   "/community/dailylesson/wordiknowupdate.ashx?contentId=174087",
                   [200,
                    { "Content-Type": "application/json" },      fake response header
                    '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true,
              "ContentId": 174087, "IsWordIKnow": false }'
                  ]
              );
               this.flashcard.fetch();
               this.server.respond();

                  expect(this.flashcard.get(‘Value’)).toEqual(‘here’);
            });
      });
});
Fake Server
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when fetch”, function() {
          beforeEach(function() {
              this.server = sinon.fakeServer.create();
              this.flashcard = new this.Flashcard({contentId: 174087});
          });
          afterEach(function() {
               this.server.restore();
          });

            it(“should get data from backend”, function() {
               this.server.respondWith(
                   "GET",
                   "/community/dailylesson/wordiknowupdate.ashx?contentId=174087",
                   [200,
                    { "Content-Type": "application/json" },
                    '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true,
              "ContentId": 174087, "IsWordIKnow": false }'
                  ]
              );                                                 fake response body
               this.flashcard.fetch();
               this.server.respond();

                  expect(this.flashcard.get(‘Value’)).toEqual(‘here’);
            });
      });
});
Fake Server
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when fetch”, function() {
          beforeEach(function() {
              this.server = sinon.fakeServer.create();
              this.flashcard = new this.Flashcard({contentId: 174087});
          });
          afterEach(function() {
               this.server.restore();
          });

            it(“should get data from backend”, function() {
               this.server.respondWith(
                   "GET",
                   "/community/dailylesson/wordiknowupdate.ashx?contentId=174087",
                   [200,
                    { "Content-Type": "application/json" },
                    '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true,
              "ContentId": 174087, "IsWordIKnow": false }'
                  ]
              );
               this.flashcard.fetch();
               this.server.respond();
                                              immediately     responds with fake data

                  expect(this.flashcard.get(‘Value’)).toEqual(‘here’);
            });
      });
});
Fake Server
describe("Flashcard model", function () {
    beforeEach(function () {
        this.Flashcard = require('models/Flashcard');
    });

      describe(“when fetch”, function() {
          beforeEach(function() {
              this.server = sinon.fakeServer.create();
              this.flashcard = new this.Flashcard({contentId: 174087});
          });
          afterEach(function() {
               this.server.restore();
          });

            it(“should get data from backend”, function() {
               this.server.respondWith(
                   "GET",
                   "/community/dailylesson/wordiknowupdate.ashx?contentId=174087",
                   [200,
                    { "Content-Type": "application/json" },
                    '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true,
              "ContentId": 174087, "IsWordIKnow": false }'
                  ]
              );
               this.flashcard.fetch();
               this.server.respond();

                  expect(this.flashcard.get(‘Value’)).toEqual(‘here’);    verify changes
            });
      });
});
Next Step
• fixture management
 • DOM
 • HTTP Response
• Code Coverage for Javascript
• Integration with CI
• Integration with jsTestDriver
Thank you
Thank you

if you like this topic please give me
Thank you

if you like this topic please give me

Mais conteúdo relacionado

Mais procurados

Writing Testable Code
Writing Testable CodeWriting Testable Code
Writing Testable Code
jameshalsall
 
Test-Driven Development
Test-Driven DevelopmentTest-Driven Development
Test-Driven Development
Meilan Ou
 
Benefit From Unit Testing In The Real World
Benefit From Unit Testing In The Real WorldBenefit From Unit Testing In The Real World
Benefit From Unit Testing In The Real World
Dror Helper
 

Mais procurados (20)

TDD - Agile
TDD - Agile TDD - Agile
TDD - Agile
 
Writing Testable Code
Writing Testable CodeWriting Testable Code
Writing Testable Code
 
Tools for Software Testing
Tools for Software TestingTools for Software Testing
Tools for Software Testing
 
Unit tests benefits
Unit tests benefitsUnit tests benefits
Unit tests benefits
 
Unit testing
Unit testing Unit testing
Unit testing
 
Unit tests & TDD
Unit tests & TDDUnit tests & TDD
Unit tests & TDD
 
Win at life with unit testing
Win at life with unit testingWin at life with unit testing
Win at life with unit testing
 
Unit Testing (C#)
Unit Testing (C#)Unit Testing (C#)
Unit Testing (C#)
 
Test-Driven Development
Test-Driven DevelopmentTest-Driven Development
Test-Driven Development
 
Benefit From Unit Testing In The Real World
Benefit From Unit Testing In The Real WorldBenefit From Unit Testing In The Real World
Benefit From Unit Testing In The Real World
 
Introduction to Acceptance Test Driven Development
Introduction to Acceptance Test Driven DevelopmentIntroduction to Acceptance Test Driven Development
Introduction to Acceptance Test Driven Development
 
Design For Testability
Design For TestabilityDesign For Testability
Design For Testability
 
An Introduction to Unit Testing
An Introduction to Unit TestingAn Introduction to Unit Testing
An Introduction to Unit Testing
 
Unit Testing
Unit TestingUnit Testing
Unit Testing
 
An Introduction to Unit Test Using NUnit
An Introduction to Unit Test Using NUnitAn Introduction to Unit Test Using NUnit
An Introduction to Unit Test Using NUnit
 
Unit Tests And Automated Testing
Unit Tests And Automated TestingUnit Tests And Automated Testing
Unit Tests And Automated Testing
 
Introduction to Test Automation
Introduction to Test AutomationIntroduction to Test Automation
Introduction to Test Automation
 
Practical unit testing in c & c++
Practical unit testing in c & c++Practical unit testing in c & c++
Practical unit testing in c & c++
 
Nunit
NunitNunit
Nunit
 
Hands-on Experience Model based testing with spec explorer
Hands-on Experience Model based testing with spec explorer Hands-on Experience Model based testing with spec explorer
Hands-on Experience Model based testing with spec explorer
 

Destaque

UNIT TESTING PPT
UNIT TESTING PPTUNIT TESTING PPT
UNIT TESTING PPT
suhasreddy1
 

Destaque (6)

960Grid 实践
960Grid 实践960Grid 实践
960Grid 实践
 
iCast2 广告代码库
iCast2 广告代码库iCast2 广告代码库
iCast2 广告代码库
 
UNIT TESTING PPT
UNIT TESTING PPTUNIT TESTING PPT
UNIT TESTING PPT
 
Lightning Talk #9: How UX and Data Storytelling Can Shape Policy by Mika Aldaba
Lightning Talk #9: How UX and Data Storytelling Can Shape Policy by Mika AldabaLightning Talk #9: How UX and Data Storytelling Can Shape Policy by Mika Aldaba
Lightning Talk #9: How UX and Data Storytelling Can Shape Policy by Mika Aldaba
 
SEO: Getting Personal
SEO: Getting PersonalSEO: Getting Personal
SEO: Getting Personal
 
Succession “Losers”: What Happens to Executives Passed Over for the CEO Job?
Succession “Losers”: What Happens to Executives Passed Over for the CEO Job? Succession “Losers”: What Happens to Executives Passed Over for the CEO Job?
Succession “Losers”: What Happens to Executives Passed Over for the CEO Job?
 

Semelhante a Client Side Unit Testing

Unit testing, UI testing and Test Driven Development in Visual Studio 2012
Unit testing, UI testing and Test Driven Development in Visual Studio 2012Unit testing, UI testing and Test Driven Development in Visual Studio 2012
Unit testing, UI testing and Test Driven Development in Visual Studio 2012
Jacinto Limjap
 
Introduction to-automated-testing
Introduction to-automated-testingIntroduction to-automated-testing
Introduction to-automated-testing
BestBrains
 

Semelhante a Client Side Unit Testing (20)

Unit testing, UI testing and Test Driven Development in Visual Studio 2012
Unit testing, UI testing and Test Driven Development in Visual Studio 2012Unit testing, UI testing and Test Driven Development in Visual Studio 2012
Unit testing, UI testing and Test Driven Development in Visual Studio 2012
 
Test Driven Development using QUnit
Test Driven Development using QUnitTest Driven Development using QUnit
Test Driven Development using QUnit
 
Software testing
Software testingSoftware testing
Software testing
 
Play with Testing on Android - Gilang Ramadhan (Academy Content Writer at Dic...
Play with Testing on Android - Gilang Ramadhan (Academy Content Writer at Dic...Play with Testing on Android - Gilang Ramadhan (Academy Content Writer at Dic...
Play with Testing on Android - Gilang Ramadhan (Academy Content Writer at Dic...
 
Software Testing Basic Concepts
Software Testing Basic ConceptsSoftware Testing Basic Concepts
Software Testing Basic Concepts
 
An introduction to Software Testing and Test Management
An introduction to Software Testing and Test ManagementAn introduction to Software Testing and Test Management
An introduction to Software Testing and Test Management
 
Enter the mind of an Agile Developer
Enter the mind of an Agile DeveloperEnter the mind of an Agile Developer
Enter the mind of an Agile Developer
 
An Introduction to Unit Testing
An Introduction to Unit TestingAn Introduction to Unit Testing
An Introduction to Unit Testing
 
Introduction to Automated Testing
Introduction to Automated TestingIntroduction to Automated Testing
Introduction to Automated Testing
 
Introduction to-automated-testing
Introduction to-automated-testingIntroduction to-automated-testing
Introduction to-automated-testing
 
Software testing
Software testing Software testing
Software testing
 
Types of testing
Types of testingTypes of testing
Types of testing
 
Unit testing (Exploring the other side as a tester)
Unit testing (Exploring the other side as a tester)Unit testing (Exploring the other side as a tester)
Unit testing (Exploring the other side as a tester)
 
Java Code Quality Tools
Java Code Quality ToolsJava Code Quality Tools
Java Code Quality Tools
 
4&5.pptx SOFTWARE TESTING UNIT-4 AND UNIT-5
4&5.pptx SOFTWARE TESTING UNIT-4 AND UNIT-54&5.pptx SOFTWARE TESTING UNIT-4 AND UNIT-5
4&5.pptx SOFTWARE TESTING UNIT-4 AND UNIT-5
 
Topic production code
Topic production codeTopic production code
Topic production code
 
Week 14 Unit Testing.pptx
Week 14  Unit Testing.pptxWeek 14  Unit Testing.pptx
Week 14 Unit Testing.pptx
 
End-end tests as first class citizens - SeleniumConf 2020
End-end tests as first class citizens - SeleniumConf 2020End-end tests as first class citizens - SeleniumConf 2020
End-end tests as first class citizens - SeleniumConf 2020
 
Fundamentals of software testing
Fundamentals of software testingFundamentals of software testing
Fundamentals of software testing
 
SOFTWARE TESTING.pptx
SOFTWARE TESTING.pptxSOFTWARE TESTING.pptx
SOFTWARE TESTING.pptx
 

Último

Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
 

Último (20)

Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of Brazil
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your Business
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
HTML Injection Attacks: Impact and Mitigation Strategies
HTML Injection Attacks: Impact and Mitigation StrategiesHTML Injection Attacks: Impact and Mitigation Strategies
HTML Injection Attacks: Impact and Mitigation Strategies
 

Client Side Unit Testing

  • 1. Client-Side Unit Testing Cloud Chen 2012/5/11
  • 3. You know you should, but you don’t
  • 4. You won’t be blamed for it
  • 6.
  • 7. There are many common issues that prevent developers for writing tests
  • 9. You think • Tests are not necessary and irrelevant
  • 10. You think • Tests are not necessary and irrelevant • Manual testing is enough
  • 11. You think • Tests are not necessary and irrelevant • Manual testing is enough • Lacking of specification
  • 12. You think • Tests are not necessary and irrelevant • Manual testing is enough • Lacking of specification • Spaghetti code are hard for testing
  • 13. You think • Tests are not necessary and irrelevant • Manual testing is enough • Lacking of specification • Spaghetti code are hard for testing • Web application needs functional testing rather than unit testing
  • 14. You think • Tests are not necessary and irrelevant • Manual testing is enough • Lacking of specification • Spaghetti code are hard for testing • Web application needs functional testing rather than unit testing • Unit testing looks like only useful for back-end code rather than front-end
  • 15. You think • Tests are not necessary and irrelevant • Manual testing is enough • Lacking of specification • Spaghetti code are hard for testing • Web application needs functional testing rather than unit testing • Unit testing looks like only useful for back-end code rather than front-end • Lazy...
  • 17. Our goal Help you start writing unit tests for front-end code
  • 19. Let’s start with another type of testing
  • 21. What is functional testing Functional testing is a type of black box testing that bases its test cases on the specifications of the software component under test. Functions are tested by feeding them input and examining the output, and internal program structure is rarely considered.
  • 22. What is functional testing Functional testing is a type of black box testing that bases its test cases on the specifications of the software component under test. Functions are tested by feeding them input and examining the output, and internal program structure is rarely considered.
  • 24. In a nutshell • Written from user perspective
  • 25. In a nutshell • Written from user perspective • Proving users are able to reproduce defined steps
  • 26. In a nutshell • Written from user perspective • Proving users are able to reproduce defined steps • Don’t need to consider internal program structure
  • 27. In a nutshell • Written from user perspective • Proving users are able to reproduce defined steps • Don’t need to consider internal program structure • Automating manual testing
  • 29. What is unit testing unit testing is a method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine if they are fit for use.
  • 30. What is unit testing unit testing is a method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine if they are fit for use.
  • 32. In a nutshell • Unit testing is complete isolation
  • 33. In a nutshell • Unit testing is complete isolation • Must irrelevant to external dependencies
  • 34. In a nutshell • Unit testing is complete isolation • Must irrelevant to external dependencies • All unreliable or slow dependencies of a tested unit should be stubbed
  • 35. In a nutshell • Unit testing is complete isolation • Must irrelevant to external dependencies • All unreliable or slow dependencies of a tested unit should be stubbed • Only the logic of that single unit is exercised
  • 36. In a nutshell • Unit testing is complete isolation • Must irrelevant to external dependencies • All unreliable or slow dependencies of a tested unit should be stubbed • Only the logic of that single unit is exercised • Must be fast
  • 37. What are differences between functional and unit testing
  • 38. What are differences between functional and unit testing unit functional perspective programmer user every module every process goal works as expected works as expected all cases coverage most user cases even edge cases result programmer :) user :)
  • 39. What are differences between functional and unit testing unit functional perspective programmer user every module every process goal works as expected works as expected all cases coverage most user cases even edge cases result programmer :) user :)
  • 40. What are differences between functional and unit testing unit functional perspective programmer user every module every process goal works as expected works as expected all cases coverage most user cases even edge cases result programmer :) user :)
  • 41. What are differences between functional and unit testing unit functional perspective programmer user every module every process goal works as expected works as expected all cases coverage most user cases even edge cases result programmer :) user :)
  • 42. What are differences between functional and unit testing unit functional perspective programmer user every module every process goal works as expected works as expected all cases coverage most user cases even edge cases result programmer :) user :)
  • 44. Hence.. • We are programmer
  • 45. Hence.. • We are programmer • We care our code quality
  • 46. Hence.. • We are programmer • We care our code quality • We should write unit test code
  • 47. Why, When of unit testing
  • 48. Why need unit testing
  • 49. Why need unit testing • Ensuring every component is bug-free
  • 50. Why need unit testing • Ensuring every component is bug-free • Ensuring every component is easy to modify
  • 51. Why need unit testing • Ensuring every component is bug-free • Ensuring every component is easy to modify • Easy to locating bug of complex logic
  • 52. Why need unit testing • Ensuring every component is bug-free • Ensuring every component is easy to modify • Easy to locating bug of complex logic • Preventing and capturing regression bug
  • 53. Why need unit testing • Ensuring every component is bug-free • Ensuring every component is easy to modify • Easy to locating bug of complex logic • Preventing and capturing regression bug • Testable code must be high readability and maintainability code
  • 54. Why need unit testing • Ensuring every component is bug-free • Ensuring every component is easy to modify • Easy to locating bug of complex logic • Preventing and capturing regression bug • Testable code must be high readability and maintainability code • and so on...
  • 55. When to do unit testing
  • 56. When to do unit testing • When you write your own classes, modules, libraries, frameworks.
  • 57. When to do unit testing • When you write your own classes, modules, libraries, frameworks. • You don’t need to write test code for fundamental framework
  • 59. testing style comparison TDD BDD based on function oriented feature oriented syntax testing language idiomatic spring from programmer stakeholder || PO accumulation test as code test as documentation
  • 60. testing style comparison TDD BDD based on function oriented feature oriented syntax testing language idiomatic spring from programmer stakeholder || PO accumulation test as code test as documentation
  • 61. testing style comparison TDD BDD based on function oriented feature oriented syntax testing language idiomatic spring from programmer stakeholder || PO accumulation test as code test as documentation
  • 62. testing style comparison TDD BDD based on function oriented feature oriented syntax testing language idiomatic spring from programmer stakeholder || PO accumulation test as code test as documentation
  • 63. testing style comparison TDD BDD based on function oriented feature oriented syntax testing language idiomatic spring from programmer stakeholder || PO accumulation test as code test as documentation
  • 65. testing style comparison • It doesn’t matter which style you choose
  • 66. testing style comparison • It doesn’t matter which style you choose • It does matter how many cases you have
  • 68. testing style comparison As we are using Scrum,
  • 69. testing style comparison As we are using Scrum, BDD is more suitable for us.
  • 71. BDD Framework • Focuses on assertion, doesn’t depend on DOM
  • 72. BDD Framework • Focuses on assertion, doesn’t depend on DOM • Succinct API
  • 73. BDD Framework • Focuses on assertion, doesn’t depend on DOM • Succinct API • Natively support Spy
  • 74. BDD Framework • Focuses on assertion, doesn’t depend on DOM • Succinct API • Natively support Spy • Support spec helper
  • 75. BDD Framework • Focuses on assertion, doesn’t depend on DOM • Succinct API • Natively support Spy • Support spec helper • Be able to extend matcher
  • 76. BDD Framework • Focuses on assertion, doesn’t depend on DOM • Succinct API • Natively support Spy • Support spec helper • Be able to extend matcher • Be able to run in several environments
  • 77. BDD Framework • Focuses on assertion, doesn’t depend on DOM • Succinct API • Natively support Spy • Support spec helper • Be able to extend matcher • Be able to run in several environments 1. Browser
  • 78. BDD Framework • Focuses on assertion, doesn’t depend on DOM • Succinct API • Natively support Spy • Support spec helper • Be able to extend matcher • Be able to run in several environments 1. Browser 2. CI
  • 79. BDD Framework • Focuses on assertion, doesn’t depend on DOM • Succinct API • Natively support Spy • Support spec helper • Be able to extend matcher • Be able to run in several environments 1. Browser 2. CI 3. NodeJS
  • 80. Typical Spec & Succinct API describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); afterEach(function () { }); describe(“when initialized”, function() { beforeEach(function() { this.flashcard = new this.Flashcard({ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }); }); it(“composite key should be composed by ContentId and IsWord properties”, function() { var expected_url = this.flashcard.get('ContentId') + '-' + this.flashcard.get('IsWord'); expect(this.flashcard.id).toEqual(expected_url); }); });
  • 81. Typical Spec & Succinct API describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); Suite }); afterEach(function () { }); describe(“when initialized”, function() { beforeEach(function() { this.flashcard = new this.Flashcard({ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }); }); it(“composite key should be composed by ContentId and IsWord properties”, function() { var expected_url = this.flashcard.get('ContentId') + '-' + this.flashcard.get('IsWord'); expect(this.flashcard.id).toEqual(expected_url); }); });
  • 82. Typical Spec & Succinct API describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); afterEach(function () { Inner Suite }); describe(“when initialized”, function() { beforeEach(function() { this.flashcard = new this.Flashcard({ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }); }); it(“composite key should be composed by ContentId and IsWord properties”, function() { var expected_url = this.flashcard.get('ContentId') + '-' + this.flashcard.get('IsWord'); expect(this.flashcard.id).toEqual(expected_url); }); });
  • 83. Typical Spec & Succinct API describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); afterEach(function () { }); describe(“when initialized”, function() { beforeEach(function() { this.flashcard = new this.Flashcard({ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, }); "IsWordIKnow": false Spec }); it(“composite key should be composed by ContentId and IsWord properties”, function() { var expected_url = this.flashcard.get('ContentId') + '-' + this.flashcard.get('IsWord'); expect(this.flashcard.id).toEqual(expected_url); }); });
  • 84. Typical Spec & Succinct API describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); afterEach(function () { }); describe(“when initialized”, function() { beforeEach(function() { this.flashcard = new this.Flashcard({ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }); }); it(“composite key should be composed by ContentId and IsWord properties”, function() { var expected_url = this.flashcard.get('ContentId') + '-' + this.flashcard.get('IsWord'); expect(this.flashcard.id).toEqual(expected_url); Expectation }); });
  • 85. Typical Spec & Succinct API describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); afterEach(function () { }); describe(“when initialized”, function() { beforeEach(function() { this.flashcard = new this.Flashcard({ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }); }); it(“composite key should be composed by ContentId and IsWord properties”, function() { var expected_url = this.flashcard.get('ContentId') + '-' + this.flashcard.get('IsWord'); expect(this.flashcard.id).toEqual(expected_url); }); }); Matcher
  • 86. Typical Spec & Succinct API describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); Setup }); afterEach(function () { }); describe(“when initialized”, function() { beforeEach(function() { this.flashcard = new this.Flashcard({ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }); }); it(“composite key should be composed by ContentId and IsWord properties”, function() { var expected_url = this.flashcard.get('ContentId') + '-' + this.flashcard.get('IsWord'); expect(this.flashcard.id).toEqual(expected_url); }); });
  • 87. Typical Spec & Succinct API describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); Setup }); afterEach(function () { }); Tear down describe(“when initialized”, function() { beforeEach(function() { this.flashcard = new this.Flashcard({ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }); }); it(“composite key should be composed by ContentId and IsWord properties”, function() { var expected_url = this.flashcard.get('ContentId') + '-' + this.flashcard.get('IsWord'); expect(this.flashcard.id).toEqual(expected_url); }); });
  • 90. Spec Helper beforeEach(function() { this.fixtures = { Flashcard: { valid: { // response starts here "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }, error: { // response starts here "ErrorCode": "101", "IsSuccess": "false" } } }
  • 91. Spec Helper beforeEach(function() { this.fixtures = { Flashcard: { valid: { // response starts here "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }, error: { // response starts here "ErrorCode": "101", "IsSuccess": "false" } } } Using this.fixtures.Flashcard.valid to access pre-defined fixture for testing when this spec file is included in your spec runner.
  • 92. Spy describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when initialized”, function() { beforeEach(function() { spyOn(jQuery, ‘ajax’); this.flashcard = new this.Flashcard(); this.flashcard.fetch(); }); it(“should communicate with back-end via http get method”, function() { expect(jQuery.ajax).toHaveBeenCalled(); expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’); }); }); });
  • 93. Spy describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when initialized”, function() { beforeEach(function() { spyOn(jQuery, ‘ajax’); spy on instance method this.flashcard = new this.Flashcard(); this.flashcard.fetch(); }); it(“should communicate with back-end via http get method”, function() { expect(jQuery.ajax).toHaveBeenCalled(); expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’); }); }); });
  • 94. Spy describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when initialized”, function() { beforeEach(function() { spyOn(jQuery, ‘ajax’); this.flashcard = new this.Flashcard(); this.flashcard.fetch(); }); original function won’t be called it(“should communicate with back-end via http get method”, function() { expect(jQuery.ajax).toHaveBeenCalled(); expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’); }); }); });
  • 95. Spy describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when initialized”, function() { beforeEach(function() { spyOn(jQuery, ‘ajax’); this.flashcard = new this.Flashcard(); this.flashcard.fetch(); }); it(“should communicate with back-end via http get method”, function() { expect(jQuery.ajax).toHaveBeenCalled(); expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’); }); }); match spied function was called });
  • 96. Spy describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when initialized”, function() { beforeEach(function() { spyOn(jQuery, ‘ajax’); this.flashcard = new this.Flashcard(); this.flashcard.fetch(); }); it(“should communicate with back-end via http get method”, function() { expect(jQuery.ajax).toHaveBeenCalled(); expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’); }); }); }); arguments of last call
  • 97. Spy describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when initialized”, function() { beforeEach(function() { spyOn(jQuery, ‘ajax’); this.flashcard = new this.Flashcard(); this.flashcard.fetch(); }); it(“should communicate with back-end via http get method”, function() { expect(jQuery.ajax).toHaveBeenCalled(); expect(jQuery.ajax.mostRecentCall.args[0].type).toEqual(‘GET’); }); }); }); arguments of last call type of first argument
  • 98. Extending Matcher <a href=”#” class=”link”>this is a link</a> expect($(‘a’).hasClass('s-selected')).toBeTruthy(); expectation failed: Expected false to be truthy.
  • 99. Extending Matcher <a href=”#” class=”link”>this is a link</a> expect($(‘a’).hasClass('s-selected')).toBeTruthy(); expectation failed: Expected false to be truthy. jasmine-jquery comes to rescue
  • 100. Matcher from jasmine jquery <a href=”#” class=”link”>this is a link</a> expect($(‘a’).hasClass('s-selected')).toBeTruthy(); expectation failed: Expected false to be truthy.
  • 101. Matcher from jasmine jquery <a href=”#” class=”link”>this is a link</a> expect($(‘a’).hasClass('s-selected')).toBeTruthy(); expectation failed: Expected false to be truthy. expect($(‘a’).toHaveClass(‘s-selected’);
  • 102. Matcher from jasmine jquery <a href=”#” class=”link”>this is a link</a> expect($(‘a’).hasClass('s-selected')).toBeTruthy(); expectation failed: Expected false to be truthy. expect($(‘a’).toHaveClass(‘s-selected’); expectation failed:
  • 103. Matcher from jasmine jquery <a href=”#” class=”link”>this is a link</a> expect($(‘a’).hasClass('s-selected')).toBeTruthy(); expectation failed: Expected false to be truthy. expect($(‘a’).toHaveClass(‘s-selected’); expectation failed: Expected '<a></a>' to have class 's-selected'.
  • 104. jasmine-jquery features • a set of custom matchers for jQuery framework • an API for handling HTML fixtures in your specs
  • 105. jasmine-jquery matchers expect(x).toBeHidden() expect(x).toBeVisible() expect(x).toHaveAttr(attributeName, attributeValue) expect(x).toHaveProp(propertyName, propertyValue) expect(x).toHaveText(string) expect(x).toHaveHtml(string) expect(x).toHaveId(id) expect(x).toBeDisabled() expect(x).toBeFocused() expect(x).toHandle(eventName)
  • 106. jasmine-jquery fixtures In myfixture.html file: <div id="my-fixture">some complex content here</div> Inside your test: loadFixtures('myfixture.html'); $('#my-fixture').myTestedPlugin(); expect($('#my-fixture')).to...;
  • 107. Running Jasmine Standalone Runner * Manually manage of your project files and specs
  • 108. Running Jasmine Dedicated Server runs jasmine Ruby jasmine Gem * Using yaml manages of your project files and specs
  • 109. Recap rules of Unit Testing • Unit testing is complete isolation • Must irrelevant to external dependencies • All unreliable or slow dependencies of a tested unit should be stubbed • Only the logic of that single unit is exercised • Must be fast
  • 110. Recap rules of Unit Testing • Unit testing is complete isolation • Must irrelevant to external dependencies • All unreliable or slow dependencies of a tested unit should be stubbed • Only the logic of that single unit is exercised • Must be fast
  • 112. Stub every dependency • Native spy feature of Jasmine is not enough
  • 113. Stub every dependency • Native spy feature of Jasmine is not enough • It doesn’t support fake timer
  • 114. Stub every dependency • Native spy feature of Jasmine is not enough • It doesn’t support fake timer • It doesn’t support fake HTTP server
  • 115. Stub every dependency • Native spy feature of Jasmine is not enough • It doesn’t support fake timer • It doesn’t support fake HTTP server • It misuses Spy and Stub
  • 116. Stub every dependency • Native spy feature of Jasmine is not enough • It doesn’t support fake timer • It doesn’t support fake HTTP server • It misuses Spy and Stub Sinon.js comes to rescue
  • 118. Spy/Stub/Mock • A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. A test spy can be an anonymous function or it can wrap an existing function.
  • 119. Spy/Stub/Mock • A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. A test spy can be an anonymous function or it can wrap an existing function. • Test stubs are functions (spies) with pre-programmed behavior. They support the full test spy API in addition to methods which can be used to alter the stub's behavior.
  • 120. Spy/Stub/Mock • A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. A test spy can be an anonymous function or it can wrap an existing function. • Test stubs are functions (spies) with pre-programmed behavior. They support the full test spy API in addition to methods which can be used to alter the stub's behavior. • Mocks (and mock expectations) are fake methods (like spies) with pre-programmed behavior (like stubs) as well as pre- programmed expectations. A mock will fail your test if it is not used as expected.
  • 124. Spy/Stub/Mock Spy Stub Mock
  • 125. Spy describe("when initialized", function () { beforeEach(function() { sinon.spy(this, "Flashcards"); create a spy for this.Flashcards }) afterEach(function() { this.Flashcards.restore(); }) it("should throw Error when cultureCode is not passed", function() { try { var flashcards = new this.Flashcards([], { }); } catch(e) {} expect(this.Flashcards.calledOnce()).toBeTruthy(); expect(this.Flashcards.threw()).toBeTruthy(); expect(this.Flashcards.calledWith([], {})).toBeTruthy(); }); });
  • 126. Spy describe("when initialized", function () { beforeEach(function() { sinon.spy(this, "Flashcards"); }) afterEach(function() { this.Flashcards.restore(); unwraps the spy }) it("should throw Error when cultureCode is not passed", function() { try { var flashcards = new this.Flashcards([], { }); } catch(e) {} expect(this.Flashcards.calledOnce()).toBeTruthy(); expect(this.Flashcards.threw()).toBeTruthy(); expect(this.Flashcards.calledWith([], {})).toBeTruthy(); }); });
  • 127. Spy describe("when initialized", function () { beforeEach(function() { sinon.spy(this, "Flashcards"); }) afterEach(function() { this.Flashcards.restore(); }) it("should throw Error when cultureCode is not passed", function() { try { var flashcards = new this.Flashcards([], { }); } catch(e) {} expect(this.Flashcards.calledOnce()).toBeTruthy(); spy was called once expect(this.Flashcards.threw()).toBeTruthy(); expect(this.Flashcards.calledWith([], {})).toBeTruthy(); }); });
  • 128. Spy describe("when initialized", function () { beforeEach(function() { sinon.spy(this, "Flashcards"); }) afterEach(function() { this.Flashcards.restore(); }) it("should throw Error when cultureCode is not passed", function() { try { var flashcards = new this.Flashcards([], { }); } catch(e) {} expect(this.Flashcards.calledOnce()).toBeTruthy(); expect(this.Flashcards.threw()).toBeTruthy(); expect(this.Flashcards.calledWith([], {})).toBeTruthy(); }); }); spy threw exception at least once
  • 129. Spy describe("when initialized", function () { beforeEach(function() { sinon.spy(this, "Flashcards"); }) afterEach(function() { this.Flashcards.restore(); }) it("should throw Error when cultureCode is not passed", function() { try { var flashcards = new this.Flashcards([], { }); } catch(e) {} expect(this.Flashcards.calledOnce()).toBeTruthy(); expect(this.Flashcards.threw()).toBeTruthy(); expect(this.Flashcards.calledWith([], {})).toBeTruthy(); }); }); spy was called at least once with provided argument
  • 130. Stub describe("when update model", function () { beforeEach(function () { this.sync = sinon.stub(Backbone, "sync"); }); afterEach(function () { create a stub for this.Flashcards this.sync.restore(); }); it("should put IsWordIKnow and IsWord properties at the end of url property", function () { this.flashcard.set('IsWordIKnow', true); var expected_url = this.flashcard.url() + '&IsWordIKnow=' + this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord'); this.flashcard.save(); expect(this.sync.getCall(0).args[2].url).toEqual(expected_url); }); });
  • 131. Stub describe("when update model", function () { beforeEach(function () { this.sync = sinon.stub(Backbone, "sync"); }); afterEach(function () { this.sync.restore(); }); it("should put IsWordIKnow and IsWord properties at the end of url property", function () { this.flashcard.set('IsWordIKnow', true); var expected_url = this.flashcard.url() + '&IsWordIKnow=' + this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord'); this.flashcard.save(); expect(this.sync.getCall(0).args[2].url).toEqual(expected_url); }); }); save method will invoke Backbone.sync that already stubbed
  • 132. Stub describe("when update model", function () { beforeEach(function () { this.sync = sinon.stub(Backbone, "sync"); }); afterEach(function () { this.sync.restore(); }); it("should put IsWordIKnow and IsWord properties at the end of url property", function () { this.flashcard.set('IsWordIKnow', true); var expected_url = this.flashcard.url() + '&IsWordIKnow=' + this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord'); this.flashcard.save(); expect(this.sync.getCall(0).args[2].url).toEqual(expected_url); }); }); original sync method won’t be called if it was stubbed So, no ajax request won’t be fired
  • 133. Stub describe("when update model", function () { beforeEach(function () { this.sync = sinon.stub(Backbone, "sync"); }); afterEach(function () { this.sync.restore(); }); it("should put IsWordIKnow and IsWord properties at the end of url property", function () { this.flashcard.set('IsWordIKnow', true); var expected_url = this.flashcard.url() + '&IsWordIKnow=' + this.flashcard.get('IsWordIKnow') + '&IsWord=' + this.flashcard.get('IsWord'); this.flashcard.save(); expect(this.sync.getCall(0).args[2].url).toEqual(expected_url); }); }); get arguments of stubbed calling (spy can also do this)
  • 134. Stub But, Spy cannot do like so: it("should always confirm every confirmation", function () { sinon.stub(window, 'confirm'); confirm.returns(true); expect(confirm('Are you sure?')).toBeTruthy(); window.confirm.restore(); } Makes window.confirm() return truth
  • 135. Stub But, Spy cannot do like so: it("should always confirm every confirmation", function () { sinon.stub(window, 'confirm'); confirm.returns(true); expect(confirm('Are you sure?')).toBeTruthy(); window.confirm.restore(); } Native confirm behavior won’t fired
  • 136. Stub But, Spy cannot do like so: it("should always confirm every confirmation", function () { sinon.stub(window, 'confirm'); confirm.returns(true); expect(confirm('Are you sure?')).toBeTruthy(); window.confirm.restore(); } Native confirm behavior won’t fired It needs user interaction to finish this test without stub. This test probably be failed if user not confirms with it. That test case is unstable.
  • 137. Mock • Mock focuses on implementation details of one method • It utilizes upfront expectation to verify details rather than asserting after the details
  • 138. Mock describe("when initialized", function () { it("should throw Error when cultureCode is not passed", function() { var myAPI = { method: function () {} }; var spy = sinon.spy(); var mock = sinon.mock(myAPI); mock.expects("method").once(); expectation upfront myAPI.method(); spy(); verify mock behavior mock.verify(); expect(spy.calledOnce).toBeTruthy(); }); });
  • 139. Without FakeTimers it("should show teacher box after 1 hour", function () { var hour = 1000 * 60 * 60; setTimeout(showTeacherbox, hour); waits(hour); must wait 1 hour...crazy runs(function() { expect($(‘#teacherbox’)).toBeVisible(); }); });
  • 140. FakeTimers it("should show teacher box after 1 hour", function () { this.clock = sinon.useFakeTimers(); var hour = 1000 * 60 * 60; create a fake timer setTimeout(showTeacherbox, hour); this.clock.tick(hour); expect($(‘#teacherbox’)).toBeVisible(); this.clock.restore(); });
  • 141. FakeTimers it("should show teacher box after 1 hour", function () { this.clock = sinon.useFakeTimers(); var hour = 1000 * 60 * 60; setTimeout(showTeacherbox, hour); tick the clock ahead 1 hour this.clock.tick(hour); expect($(‘#teacherbox’)).toBeVisible(); this.clock.restore(); });
  • 142. FakeTimers it("should show teacher box after 1 hour", function () { this.clock = sinon.useFakeTimers(); var hour = 1000 * 60 * 60; setTimeout(showTeacherbox, hour); this.clock.tick(hour - 1); expect($(‘#teacherbox’)).toBeHidden(); won’t happen this.clock.tick(1); expect($(‘#teacherbox’)).toBeVisible(); will happen this.clock.restore(); });
  • 143. Fake Server describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }' ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); }); });
  • 144. Fake Server describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when fetch”, function() { beforeEach(function() { Fake server stub XHR this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }' ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); }); });
  • 145. Fake Server describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); responds to given URL and HTTP method it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }' ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); }); });
  • 146. Fake Server describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( given HTTP method "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }' ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); }); });
  • 147. Fake Server describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( given URL "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }' ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); }); });
  • 148. Fake Server describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, fake response header '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }' ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); }); });
  • 149. Fake Server describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }' ] ); fake response body this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); }); });
  • 150. Fake Server describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }' ] ); this.flashcard.fetch(); this.server.respond(); immediately responds with fake data expect(this.flashcard.get(‘Value’)).toEqual(‘here’); }); }); });
  • 151. Fake Server describe("Flashcard model", function () { beforeEach(function () { this.Flashcard = require('models/Flashcard'); }); describe(“when fetch”, function() { beforeEach(function() { this.server = sinon.fakeServer.create(); this.flashcard = new this.Flashcard({contentId: 174087}); }); afterEach(function() { this.server.restore(); }); it(“should get data from backend”, function() { this.server.respondWith( "GET", "/community/dailylesson/wordiknowupdate.ashx?contentId=174087", [200, { "Content-Type": "application/json" }, '{ "Value": "here", "Translation": "这儿", "Audio": "here_en.mp3", "IsWord": true, "ContentId": 174087, "IsWordIKnow": false }' ] ); this.flashcard.fetch(); this.server.respond(); expect(this.flashcard.get(‘Value’)).toEqual(‘here’); verify changes }); }); });
  • 152. Next Step • fixture management • DOM • HTTP Response • Code Coverage for Javascript • Integration with CI • Integration with jsTestDriver
  • 153.
  • 155. Thank you if you like this topic please give me
  • 156. Thank you if you like this topic please give me

Notas do Editor

  1. \n
  2. \n
  3. \n
  4. \n
  5. \n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. \n
  13. \n
  14. \n
  15. \n
  16. &amp;#x518D;&amp;#x5F00;&amp;#x59CB;&amp;#x5355;&amp;#x5143;&amp;#x6D4B;&amp;#x8BD5;&amp;#x4E4B;&amp;#x524D;, &amp;#x5148;&amp;#x4E86;&amp;#x89E3;&amp;#x4E00;&amp;#x4E0B;&amp;#x53E6;&amp;#x5916;&amp;#x4E00;&amp;#x79CD;&amp;#x6D4B;&amp;#x8BD5;. &amp;#x8BA4;&amp;#x6E05;&amp;#x5404;&amp;#x79CD;&amp;#x7C7B;&amp;#x578B;&amp;#x6D4B;&amp;#x8BD5;&amp;#x4E4B;&amp;#x95F4;&amp;#x7684;&amp;#x5DEE;&amp;#x522B;\n
  17. \n
  18. \n
  19. \n
  20. &amp;#x4F7F;&amp;#x624B;&amp;#x52A8;&amp;#x6D4B;&amp;#x8BD5;&amp;#x81EA;&amp;#x52A8;&amp;#x5316;\n
  21. &amp;#x4F7F;&amp;#x624B;&amp;#x52A8;&amp;#x6D4B;&amp;#x8BD5;&amp;#x81EA;&amp;#x52A8;&amp;#x5316;\n
  22. &amp;#x4F7F;&amp;#x624B;&amp;#x52A8;&amp;#x6D4B;&amp;#x8BD5;&amp;#x81EA;&amp;#x52A8;&amp;#x5316;\n
  23. &amp;#x4F7F;&amp;#x624B;&amp;#x52A8;&amp;#x6D4B;&amp;#x8BD5;&amp;#x81EA;&amp;#x52A8;&amp;#x5316;\n
  24. \n
  25. &amp;#x5355;&amp;#x5143;&amp;#x6D4B;&amp;#x8BD5;&amp;#x662F;&amp;#x4E00;&amp;#x79CD;&amp;#x65B9;&amp;#x6CD5;, &amp;#x6E90;&amp;#x7801;&amp;#x4E2D;&amp;#x72EC;&amp;#x7ACB;&amp;#x7684;&amp;#x5355;&amp;#x5143;\n
  26. &amp;#x6CE8;&amp;#x610F;&amp;#x72EC;&amp;#x7ACB;&amp;#x7684;&amp;#x5355;&amp;#x5143;&amp;#x5E76;&amp;#x4E0D;&amp;#x4E00;&amp;#x5B9A;&amp;#x662F;&amp;#x5355;&amp;#x72EC;&amp;#x7684;&amp;#x65B9;&amp;#x6CD5;, \n
  27. &amp;#x5FC5;&amp;#x987B;&amp;#x548C;&amp;#x5916;&amp;#x90E8;&amp;#x4F9D;&amp;#x8D56;&amp;#x5173;&amp;#x7CFB;&amp;#x4E0D;&amp;#x76F8;&amp;#x5173;, &amp;#x4FDD;&amp;#x6301;&amp;#x5355;&amp;#x5143;&amp;#x7684;&amp;#x72EC;&amp;#x7ACB;&amp;#x6027;, &amp;#x662F;&amp;#x4E3A;&amp;#x4E86;&amp;#x4FDD;&amp;#x8BC1;&amp;#x9519;&amp;#x8BEF;&amp;#x4E0D;&amp;#x662F;&amp;#x7531;&amp;#x5916;&amp;#x90E8;&amp;#x7684;&amp;#x4EE3;&amp;#x7801;&amp;#x5F15;&amp;#x8D77;&amp;#x7684;\n&amp;#x4E0D;&amp;#x7A33;&amp;#x5B9A;&amp;#x7684;&amp;#x4F9D;&amp;#x8D56;&amp;#x5173;&amp;#x7CFB;, &amp;#x5982;HTTP Request, timer, slow animation, &amp;#x9700;&amp;#x8981;&amp;#x88AB;stubbed\n\n
  28. &amp;#x5FC5;&amp;#x987B;&amp;#x548C;&amp;#x5916;&amp;#x90E8;&amp;#x4F9D;&amp;#x8D56;&amp;#x5173;&amp;#x7CFB;&amp;#x4E0D;&amp;#x76F8;&amp;#x5173;, &amp;#x4FDD;&amp;#x6301;&amp;#x5355;&amp;#x5143;&amp;#x7684;&amp;#x72EC;&amp;#x7ACB;&amp;#x6027;, &amp;#x662F;&amp;#x4E3A;&amp;#x4E86;&amp;#x4FDD;&amp;#x8BC1;&amp;#x9519;&amp;#x8BEF;&amp;#x4E0D;&amp;#x662F;&amp;#x7531;&amp;#x5916;&amp;#x90E8;&amp;#x7684;&amp;#x4EE3;&amp;#x7801;&amp;#x5F15;&amp;#x8D77;&amp;#x7684;\n&amp;#x4E0D;&amp;#x7A33;&amp;#x5B9A;&amp;#x7684;&amp;#x4F9D;&amp;#x8D56;&amp;#x5173;&amp;#x7CFB;, &amp;#x5982;HTTP Request, timer, slow animation, &amp;#x9700;&amp;#x8981;&amp;#x88AB;stubbed\n\n
  29. &amp;#x5FC5;&amp;#x987B;&amp;#x548C;&amp;#x5916;&amp;#x90E8;&amp;#x4F9D;&amp;#x8D56;&amp;#x5173;&amp;#x7CFB;&amp;#x4E0D;&amp;#x76F8;&amp;#x5173;, &amp;#x4FDD;&amp;#x6301;&amp;#x5355;&amp;#x5143;&amp;#x7684;&amp;#x72EC;&amp;#x7ACB;&amp;#x6027;, &amp;#x662F;&amp;#x4E3A;&amp;#x4E86;&amp;#x4FDD;&amp;#x8BC1;&amp;#x9519;&amp;#x8BEF;&amp;#x4E0D;&amp;#x662F;&amp;#x7531;&amp;#x5916;&amp;#x90E8;&amp;#x7684;&amp;#x4EE3;&amp;#x7801;&amp;#x5F15;&amp;#x8D77;&amp;#x7684;\n&amp;#x4E0D;&amp;#x7A33;&amp;#x5B9A;&amp;#x7684;&amp;#x4F9D;&amp;#x8D56;&amp;#x5173;&amp;#x7CFB;, &amp;#x5982;HTTP Request, timer, slow animation, &amp;#x9700;&amp;#x8981;&amp;#x88AB;stubbed\n\n
  30. &amp;#x5FC5;&amp;#x987B;&amp;#x548C;&amp;#x5916;&amp;#x90E8;&amp;#x4F9D;&amp;#x8D56;&amp;#x5173;&amp;#x7CFB;&amp;#x4E0D;&amp;#x76F8;&amp;#x5173;, &amp;#x4FDD;&amp;#x6301;&amp;#x5355;&amp;#x5143;&amp;#x7684;&amp;#x72EC;&amp;#x7ACB;&amp;#x6027;, &amp;#x662F;&amp;#x4E3A;&amp;#x4E86;&amp;#x4FDD;&amp;#x8BC1;&amp;#x9519;&amp;#x8BEF;&amp;#x4E0D;&amp;#x662F;&amp;#x7531;&amp;#x5916;&amp;#x90E8;&amp;#x7684;&amp;#x4EE3;&amp;#x7801;&amp;#x5F15;&amp;#x8D77;&amp;#x7684;\n&amp;#x4E0D;&amp;#x7A33;&amp;#x5B9A;&amp;#x7684;&amp;#x4F9D;&amp;#x8D56;&amp;#x5173;&amp;#x7CFB;, &amp;#x5982;HTTP Request, timer, slow animation, &amp;#x9700;&amp;#x8981;&amp;#x88AB;stubbed\n\n
  31. &amp;#x5FC5;&amp;#x987B;&amp;#x548C;&amp;#x5916;&amp;#x90E8;&amp;#x4F9D;&amp;#x8D56;&amp;#x5173;&amp;#x7CFB;&amp;#x4E0D;&amp;#x76F8;&amp;#x5173;, &amp;#x4FDD;&amp;#x6301;&amp;#x5355;&amp;#x5143;&amp;#x7684;&amp;#x72EC;&amp;#x7ACB;&amp;#x6027;, &amp;#x662F;&amp;#x4E3A;&amp;#x4E86;&amp;#x4FDD;&amp;#x8BC1;&amp;#x9519;&amp;#x8BEF;&amp;#x4E0D;&amp;#x662F;&amp;#x7531;&amp;#x5916;&amp;#x90E8;&amp;#x7684;&amp;#x4EE3;&amp;#x7801;&amp;#x5F15;&amp;#x8D77;&amp;#x7684;\n&amp;#x4E0D;&amp;#x7A33;&amp;#x5B9A;&amp;#x7684;&amp;#x4F9D;&amp;#x8D56;&amp;#x5173;&amp;#x7CFB;, &amp;#x5982;HTTP Request, timer, slow animation, &amp;#x9700;&amp;#x8981;&amp;#x88AB;stubbed\n\n
  32. &amp;#x5355;&amp;#x5143;&amp;#x6D4B;&amp;#x8BD5;&amp;#x53C8;&amp;#x79F0;&amp;#x6A21;&amp;#x5757;&amp;#x6D4B;&amp;#x8BD5;\n
  33. &amp;#x5355;&amp;#x5143;&amp;#x6D4B;&amp;#x8BD5;&amp;#x53C8;&amp;#x79F0;&amp;#x6A21;&amp;#x5757;&amp;#x6D4B;&amp;#x8BD5;\n
  34. &amp;#x5355;&amp;#x5143;&amp;#x6D4B;&amp;#x8BD5;&amp;#x53C8;&amp;#x79F0;&amp;#x6A21;&amp;#x5757;&amp;#x6D4B;&amp;#x8BD5;\n
  35. &amp;#x5355;&amp;#x5143;&amp;#x6D4B;&amp;#x8BD5;&amp;#x53C8;&amp;#x79F0;&amp;#x6A21;&amp;#x5757;&amp;#x6D4B;&amp;#x8BD5;\n
  36. &amp;#x5355;&amp;#x5143;&amp;#x6D4B;&amp;#x8BD5;&amp;#x53C8;&amp;#x79F0;&amp;#x6A21;&amp;#x5757;&amp;#x6D4B;&amp;#x8BD5;\n
  37. &amp;#x8FD9;&amp;#x91CC;&amp;#x5E76;&amp;#x4E0D;&amp;#x662F;&amp;#x8BF4;&amp;#x6211;&amp;#x4EEC;&amp;#x4E0D;&amp;#x9700;&amp;#x8981;&amp;#x5199;functional testing. Functional Testing&amp;#x662F;&amp;#x4E00;&amp;#x79CD;&amp;#x8865;&amp;#x5145;, &amp;#x548C;unit testing&amp;#x4E00;&amp;#x6837;&amp;#x5FC5;&amp;#x4E0D;&amp;#x53EF;&amp;#x7F3A;\n&amp;#x6211;&amp;#x4EEC;&amp;#x8FD9;&amp;#x91CC;&amp;#x53EA;focus unit testing\n
  38. &amp;#x8FD9;&amp;#x91CC;&amp;#x5E76;&amp;#x4E0D;&amp;#x662F;&amp;#x8BF4;&amp;#x6211;&amp;#x4EEC;&amp;#x4E0D;&amp;#x9700;&amp;#x8981;&amp;#x5199;functional testing. Functional Testing&amp;#x662F;&amp;#x4E00;&amp;#x79CD;&amp;#x8865;&amp;#x5145;, &amp;#x548C;unit testing&amp;#x4E00;&amp;#x6837;&amp;#x5FC5;&amp;#x4E0D;&amp;#x53EF;&amp;#x7F3A;\n&amp;#x6211;&amp;#x4EEC;&amp;#x8FD9;&amp;#x91CC;&amp;#x53EA;focus unit testing\n
  39. &amp;#x8FD9;&amp;#x91CC;&amp;#x5E76;&amp;#x4E0D;&amp;#x662F;&amp;#x8BF4;&amp;#x6211;&amp;#x4EEC;&amp;#x4E0D;&amp;#x9700;&amp;#x8981;&amp;#x5199;functional testing. Functional Testing&amp;#x662F;&amp;#x4E00;&amp;#x79CD;&amp;#x8865;&amp;#x5145;, &amp;#x548C;unit testing&amp;#x4E00;&amp;#x6837;&amp;#x5FC5;&amp;#x4E0D;&amp;#x53EF;&amp;#x7F3A;\n&amp;#x6211;&amp;#x4EEC;&amp;#x8FD9;&amp;#x91CC;&amp;#x53EA;focus unit testing\n
  40. \n
  41. \n
  42. \n
  43. \n
  44. \n
  45. \n
  46. \n
  47. \n
  48. \n
  49. &amp;#x9762;&amp;#x5411;&amp;#x51FD;&amp;#x6570;, &amp;#x5173;&amp;#x6CE8;&amp;#x4EE3;&amp;#x7801;&amp;#x5B9E;&amp;#x73B0;, &amp;#x6BD4;&amp;#x8F83;&amp;#x5728;&amp;#x610F;&amp;#x4EE3;&amp;#x7801;&amp;#x8986;&amp;#x76D6;&amp;#x7387; / &amp;#x9762;&amp;#x5411;&amp;#x529F;&amp;#x80FD;, &amp;#x5173;&amp;#x6CE8;&amp;#x4E1A;&amp;#x52A1;, &amp;#x7531;&amp;#x5916;&amp;#x53CA;&amp;#x5185;\n&amp;#x8F83;&amp;#x4E13;&amp;#x4E1A;&amp;#x5316;&amp;#x7684;&amp;#x6D4B;&amp;#x8BD5;&amp;#x8BED;&amp;#x53E5; / &amp;#x7B26;&amp;#x5408;&amp;#x8BED;&amp;#x8A00;&amp;#x4E60;&amp;#x60EF;, &amp;#x63A5;&amp;#x8FD1;&amp;#x81EA;&amp;#x7136;&amp;#x8BED;&amp;#x8A00;\n
  50. &amp;#x9762;&amp;#x5411;&amp;#x51FD;&amp;#x6570;, &amp;#x5173;&amp;#x6CE8;&amp;#x4EE3;&amp;#x7801;&amp;#x5B9E;&amp;#x73B0;, &amp;#x6BD4;&amp;#x8F83;&amp;#x5728;&amp;#x610F;&amp;#x4EE3;&amp;#x7801;&amp;#x8986;&amp;#x76D6;&amp;#x7387; / &amp;#x9762;&amp;#x5411;&amp;#x529F;&amp;#x80FD;, &amp;#x5173;&amp;#x6CE8;&amp;#x4E1A;&amp;#x52A1;, &amp;#x7531;&amp;#x5916;&amp;#x53CA;&amp;#x5185;\n&amp;#x8F83;&amp;#x4E13;&amp;#x4E1A;&amp;#x5316;&amp;#x7684;&amp;#x6D4B;&amp;#x8BD5;&amp;#x8BED;&amp;#x53E5; / &amp;#x7B26;&amp;#x5408;&amp;#x8BED;&amp;#x8A00;&amp;#x4E60;&amp;#x60EF;, &amp;#x63A5;&amp;#x8FD1;&amp;#x81EA;&amp;#x7136;&amp;#x8BED;&amp;#x8A00;\n
  51. &amp;#x9762;&amp;#x5411;&amp;#x51FD;&amp;#x6570;, &amp;#x5173;&amp;#x6CE8;&amp;#x4EE3;&amp;#x7801;&amp;#x5B9E;&amp;#x73B0;, &amp;#x6BD4;&amp;#x8F83;&amp;#x5728;&amp;#x610F;&amp;#x4EE3;&amp;#x7801;&amp;#x8986;&amp;#x76D6;&amp;#x7387; / &amp;#x9762;&amp;#x5411;&amp;#x529F;&amp;#x80FD;, &amp;#x5173;&amp;#x6CE8;&amp;#x4E1A;&amp;#x52A1;, &amp;#x7531;&amp;#x5916;&amp;#x53CA;&amp;#x5185;\n&amp;#x8F83;&amp;#x4E13;&amp;#x4E1A;&amp;#x5316;&amp;#x7684;&amp;#x6D4B;&amp;#x8BD5;&amp;#x8BED;&amp;#x53E5; / &amp;#x7B26;&amp;#x5408;&amp;#x8BED;&amp;#x8A00;&amp;#x4E60;&amp;#x60EF;, &amp;#x63A5;&amp;#x8FD1;&amp;#x81EA;&amp;#x7136;&amp;#x8BED;&amp;#x8A00;\n
  52. &amp;#x9762;&amp;#x5411;&amp;#x51FD;&amp;#x6570;, &amp;#x5173;&amp;#x6CE8;&amp;#x4EE3;&amp;#x7801;&amp;#x5B9E;&amp;#x73B0;, &amp;#x6BD4;&amp;#x8F83;&amp;#x5728;&amp;#x610F;&amp;#x4EE3;&amp;#x7801;&amp;#x8986;&amp;#x76D6;&amp;#x7387; / &amp;#x9762;&amp;#x5411;&amp;#x529F;&amp;#x80FD;, &amp;#x5173;&amp;#x6CE8;&amp;#x4E1A;&amp;#x52A1;, &amp;#x7531;&amp;#x5916;&amp;#x53CA;&amp;#x5185;\n&amp;#x8F83;&amp;#x4E13;&amp;#x4E1A;&amp;#x5316;&amp;#x7684;&amp;#x6D4B;&amp;#x8BD5;&amp;#x8BED;&amp;#x53E5; / &amp;#x7B26;&amp;#x5408;&amp;#x8BED;&amp;#x8A00;&amp;#x4E60;&amp;#x60EF;, &amp;#x63A5;&amp;#x8FD1;&amp;#x81EA;&amp;#x7136;&amp;#x8BED;&amp;#x8A00;\n
  53. &amp;#x9762;&amp;#x5411;&amp;#x51FD;&amp;#x6570;, &amp;#x5173;&amp;#x6CE8;&amp;#x4EE3;&amp;#x7801;&amp;#x5B9E;&amp;#x73B0;, &amp;#x6BD4;&amp;#x8F83;&amp;#x5728;&amp;#x610F;&amp;#x4EE3;&amp;#x7801;&amp;#x8986;&amp;#x76D6;&amp;#x7387; / &amp;#x9762;&amp;#x5411;&amp;#x529F;&amp;#x80FD;, &amp;#x5173;&amp;#x6CE8;&amp;#x4E1A;&amp;#x52A1;, &amp;#x7531;&amp;#x5916;&amp;#x53CA;&amp;#x5185;\n&amp;#x8F83;&amp;#x4E13;&amp;#x4E1A;&amp;#x5316;&amp;#x7684;&amp;#x6D4B;&amp;#x8BD5;&amp;#x8BED;&amp;#x53E5; / &amp;#x7B26;&amp;#x5408;&amp;#x8BED;&amp;#x8A00;&amp;#x4E60;&amp;#x60EF;, &amp;#x63A5;&amp;#x8FD1;&amp;#x81EA;&amp;#x7136;&amp;#x8BED;&amp;#x8A00;\n
  54. \n
  55. \n
  56. \n
  57. \n
  58. \n
  59. \n
  60. \n
  61. \n
  62. \n
  63. \n
  64. \n
  65. \n
  66. \n
  67. \n
  68. \n
  69. \n
  70. \n
  71. \n
  72. \n
  73. \n
  74. \n
  75. \n
  76. \n
  77. \n
  78. \n
  79. \n
  80. Jasmine&amp;#x81EA;&amp;#x5E26;&amp;#x7684;Spy&amp;#x884C;&amp;#x4E3A;&amp;#x6709;&amp;#x4E9B;&amp;#x5947;&amp;#x602A;, spy&amp;#x5E94;&amp;#x8BE5;&amp;#x8BBE;&amp;#x8BA1;&amp;#x4E3A;&amp;#x4E0D;&amp;#x963B;&amp;#x6B62;&amp;#x539F;&amp;#x59CB;&amp;#x7684;&amp;#x884C;&amp;#x4E3A;\n
  81. \n
  82. \n
  83. \n
  84. \n
  85. \n
  86. \n
  87. \n
  88. \n
  89. \n
  90. \n
  91. \n
  92. \n
  93. \n
  94. \n
  95. \n
  96. \n
  97. \n
  98. \n
  99. \n
  100. \n
  101. \n
  102. \n
  103. \n
  104. \n
  105. \n
  106. \n
  107. \n
  108. \n
  109. \n
  110. \n
  111. \n
  112. \n
  113. stub&amp;#x548C;spy&amp;#x7684;&amp;#x6700;&amp;#x5927;&amp;#x533A;&amp;#x522B;&amp;#x662F;stub&amp;#x540E;&amp;#x7684;&amp;#x65B9;&amp;#x6CD5;, &amp;#x539F;&amp;#x59CB;&amp;#x884C;&amp;#x4E3A;&amp;#x662F;&amp;#x4E0D;&amp;#x4F1A;&amp;#x88AB;&amp;#x6267;&amp;#x884C;&amp;#x7684;.\n
  114. \n
  115. \n
  116. stub&amp;#x53EF;&amp;#x4EE5;&amp;#x505A;&amp;#x66F4;&amp;#x591A;, &amp;#x4E0D;&amp;#x4EC5;&amp;#x4EC5;&amp;#x662F;&amp;#x8FD4;&amp;#x56DE;&amp;#x503C;, &amp;#x63A7;&amp;#x5236;&amp;#x56DE;&amp;#x8C03;&amp;#x51FD;&amp;#x6570;&amp;#x7684;&amp;#x53C2;&amp;#x6570;&amp;#x4E5F;&amp;#x662F;&amp;#x53EF;&amp;#x884C;&amp;#x7684;.\n
  117. \n
  118. \n
  119. \n
  120. \n
  121. \n
  122. \n
  123. \n
  124. \n
  125. \n
  126. \n
  127. \n
  128. \n
  129. \n
  130. \n
  131. \n
  132. \n
  133. \n
  134. \n
  135. \n