SlideShare a Scribd company logo
1 of 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

More Related Content

What's hot

Writing Testable Code
Writing Testable CodeWriting Testable Code
Writing Testable Codejameshalsall
 
Tools for Software Testing
Tools for Software TestingTools for Software Testing
Tools for Software TestingMohammed Moishin
 
Unit testing
Unit testing Unit testing
Unit testing dubbu
 
Unit tests & TDD
Unit tests & TDDUnit tests & TDD
Unit tests & TDDDror Helper
 
Win at life with unit testing
Win at life with unit testingWin at life with unit testing
Win at life with unit testingmarkstory
 
Test-Driven Development
Test-Driven DevelopmentTest-Driven Development
Test-Driven DevelopmentMeilan 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 WorldDror Helper
 
Introduction to Acceptance Test Driven Development
Introduction to Acceptance Test Driven DevelopmentIntroduction to Acceptance Test Driven Development
Introduction to Acceptance Test Driven DevelopmentSteven Mak
 
Design For Testability
Design For TestabilityDesign For Testability
Design For TestabilityWill Iverson
 
An Introduction to Unit Testing
An Introduction to Unit TestingAn Introduction to Unit Testing
An Introduction to Unit TestingJoe Tremblay
 
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 NUnitweili_at_slideshare
 
Unit Tests And Automated Testing
Unit Tests And Automated TestingUnit Tests And Automated Testing
Unit Tests And Automated TestingLee Englestone
 
Introduction to Test Automation
Introduction to Test AutomationIntroduction to Test Automation
Introduction to Test AutomationPekka Klärck
 
Practical unit testing in c & c++
Practical unit testing in c & c++Practical unit testing in c & c++
Practical unit testing in c & c++Matt Hargett
 
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 Rachid Kherrazi
 

What's hot (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
 

Viewers also liked

960Grid 实践
960Grid 实践960Grid 实践
960Grid 实践cloud chen
 
iCast2 广告代码库
iCast2 广告代码库iCast2 广告代码库
iCast2 广告代码库cloud chen
 
UNIT TESTING PPT
UNIT TESTING PPTUNIT TESTING PPT
UNIT TESTING PPTsuhasreddy1
 
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 Aldabaux singapore
 
SEO: Getting Personal
SEO: Getting PersonalSEO: Getting Personal
SEO: Getting PersonalKirsty Hulse
 

Viewers also liked (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?
 

Similar to 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 2012Jacinto Limjap
 
Test Driven Development using QUnit
Test Driven Development using QUnitTest Driven Development using QUnit
Test Driven Development using QUnitsatejsahu
 
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...DicodingEvent
 
Software Testing Basic Concepts
Software Testing Basic ConceptsSoftware Testing Basic Concepts
Software Testing Basic Conceptswesovi
 
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 ManagementAnuraj S.L
 
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 DeveloperBSGAfrica
 
An Introduction to Unit Testing
An Introduction to Unit TestingAn Introduction to Unit Testing
An Introduction to Unit TestingSahar Nofal
 
Introduction to Automated Testing
Introduction to Automated TestingIntroduction to Automated Testing
Introduction to Automated TestingLars Thorup
 
Introduction to-automated-testing
Introduction to-automated-testingIntroduction to-automated-testing
Introduction to-automated-testingBestBrains
 
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)Abhijeet Vaikar
 
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-5hemasubbu08
 
Topic production code
Topic production codeTopic production code
Topic production codeKavi Kumar
 
Week 14 Unit Testing.pptx
Week 14  Unit Testing.pptxWeek 14  Unit Testing.pptx
Week 14 Unit Testing.pptxmianshafa
 
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 2020Abhijeet Vaikar
 
Fundamentals of software testing
Fundamentals of software testingFundamentals of software testing
Fundamentals of software testingNoha Gamal
 
SOFTWARE TESTING.pptx
SOFTWARE TESTING.pptxSOFTWARE TESTING.pptx
SOFTWARE TESTING.pptxssrpr
 

Similar to 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
 

Recently uploaded

Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CVKhem
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProduct Anonymous
 
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 TerraformAndrey Devyatkin
 
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...Drew Madelung
 
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...DianaGray10
 
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 FresherRemote DBA Services
 
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 Takeoffsammart93
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
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 slidevu2urc
 
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 textsMaria Levchenko
 
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 StrategiesBoston Institute of Analytics
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc
 
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)wesley chun
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 

Recently uploaded (20)

Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
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
 
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...
 
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...
 
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
 
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
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
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
 
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
 
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
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
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)
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 

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

Editor's Notes

  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