Skip to content

Latest commit

 

History

History

07-TDD

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Javascript Unit Testings

##1.- Unit Testings

  • A Unit testing is a piece of cide that serves to check if another piece of code works properly. It is code thar serves to test another code.

  • These unit testings should:

    • be able of launching them automatically (this is specially important for a continuous integration)
    • test the maximum code possible (code coverage as high as possible)
    • be able of be executed as many times as needed
    • be independent. The execution os one test, shouldn't affect the execution of the other.
    • maintain the quality of the code (code convention, good practices, documentation,...)
  • As long as the source code in a project grows, it gets more and more important being able to check in a simple way that the changes and the new code doesn't break anything of the existing code

  • In Javascript this is even more important, because having the tests we can check automatically that our code works well in different environments (IE, Firefox, Chrome,...)

The unit (... to be tested)

  • IN TDD, the unit is the smallest piece of code that can be tested. Most of the times this unit has a correspondence with a function/method.

  • The test is usually also a method/function so a unit testing use to be a method (test) that test another method (unit)

  • Sometimes several unit testings are written for the same method/object having, for example, every one of them testing a specific behaviuour. These tests that test a group of related code (like an object w/ different methods) are usually grouped in a test case

  • Test Cases are usually also grouped in test suites

###Code coverage

  • The code coverage is a way of measure the proportion of aource code that is really being tested by a test suite

  • The code coverage is usually measured by a software that analizes the code and the tests. For PHP PHPUnit with XDebug is usually used. For JS, we can Sonar or Istambul with Karma

My first Unit Testing

This is the code to test (the 'unit')

  function add(a, b) {
      return a + b;
  }

And this is the code we're using to test it ( the 'testing')

  // Test function
  if (add(1, 2) !== 3) {
      document.write('<p style="color: red;">add(1, 2) does not add up to 3</p>');
  } else {  
      document.write('<p style="color: green;">add(1, 2) OK</p>');
  }

  if (add("2", 2) !== 4) {
      var msg = "Numbers as strings don't add";
      document.write('<p style="color: red;">' + msg + '</p>');
  } else {
      document.write('<p style="color: green;">add("2", 2) OK</p>');
  }

We can execute this test from here

This example is very simple but illustrate several things about unit testings:

  • The function is tested from several angles (we check what the function returns when passing different types of data to it)
  • We compare the returned value with an expected value
  • If these values match we show a green OK
  • If these values doesn't match we show an error message to help us locate the problem

The previous example didn't pass all the tests, but if we improve the code

  function add(a, b) {
      return parseInt(a) + parseInt(b);
  }

This new code pass the tests, so it follows the criteria especified in the test

##2.- TDD y BDD

###Test Driven Development (TDD)

  • TDD (Test Driven Development) is a methodology, way of programming, a workflow, that basically consist in doing the tests first (specifying what our code should do) and after that, do the code that pass these tests

  • The recommended workflow in TDD is:

    1. We write the tests for a new feature (assuming method names, input parameters, returned values...)
    2. We execute the tests and check they fail (we still haven't written the code to be tested)
    3. We write the most simple solution that pass the tests
    4. We refactor the code (cleaner and more efficient code that still pass the tests)
    5. We repeat these steps for another feature
  • By applying TDD we can focus in the interface (API, methods, inputs & outputs) more than in the details of the implementation

###Behavior Driven Development (BDD)

  • BDD (Behavior Driven Development) is a specialized version of TDD that is more focused on testing (specifications of) behaviours of software

  • It uses a more human language to write the tests and is not focused as much in how the API should work and more in testing the specific feature does what is expected to

##3.- Testing Frameworks

Looking for a better JavaScript unit test tool
Heroes of JavaScript and Testing

  • There are a few Frameworks that will ease to us the task of doing the tests of our code

  • These frameworks offer some assertions that we can use to write the tests

###Jasmine

Jasmine is a framework used to test Javascript code oriented to BDD (to test behaviours), but it can also be used for TDD (testing API).

A test suite in Jasmine is declared as a global function describe that recevies:

  • the name of the suite
  • a function with code that implements the test

The specs are declared with the global function it that receives:

  • a description of the specification
  • a function with one or more expectations

The expectations (expected behaviours) are described with the function expect and a matcher (toBe, toEqual, ...):

  describe("A suite", function() {
    it("contains spec with an expectation", function() {
        expect(true).toBe(true);
      });
  });

Some matchers that Jasmine offer by default (we can build our own) are:

  • expect(x).toEqual(y);
    compares objects or primitives x and y and passes if they are equivalent

  • expect(x).toBe(y);
    compares objects or primitives x and y and passes if they are the same object

  • expect(x).toMatch(pattern);
    compares x to string or regular expression pattern and passes if they match

  • expect(x).toBeDefined();
    passes if x is not undefined

  • expect(x).toBeUndefined();
    passes if x is undefined

  • expect(x).toBeNull();
    passes if x is null

  • expect(x).toBeTruthy();
    passes if x evaluates to true

  • expect(x).toBeFalsy();
    passes if x evaluates to false

  • expect(x).toContain(y);
    passes if array or string x contains y

  • expect(x).toBeLessThan(y);
    passes if x is less than y

  • expect(x).toBeGreaterThan(y);
    passes if x is greater than y

  • expect(function(){fn();}).toThrow(e);
    passes if function fn throws exception e when executed

For this piece of code:

  // your applications custom code
  function addValues( a, b ) {
      return a + b;
  };

A Unit Testing in Jasmine could be:

  // the Jasmine test code
  describe("addValues(a, b) function", function() {
      it("should equal 3", function(){
          expect( addValues(1, 2) ).toBe( 3 );
      });
      it("should equal 3.75", function(){
          expect( addValues(1.75, 2) ).toBe( 3.75 );
       });
      it("should NOT equal '3' as a String", function(){
          expect( addValues(1, 2) ).not.toBe( "3" );
      });
  });

More info:

###QUnit

QUnit is a unit testing framework (for client side) that allow us to test our javascript code in a simple way.

It is the framework used to test the projects jQuery, jQuery UI, jQuery Mobile... and even itself (QUnit)

Some assertions:

  • ok( state, message )
    A boolean assertion, equivalent to CommonJS's assert.ok() and JUnit's assertTrue(). Passes if the first argument is truthy.
  // Let's test this function
  function isEven(val) {
    return val % 2 === 0;
  }
  test('isEven()', function() {
    ok(isEven(0), 'Zero is an even number');
    ok(isEven(2), 'So is two');
    ok(isEven(-4), 'So is negative four');
    ok(!isEven(1), 'One is not an even number');
    ok(!isEven(-7), 'Neither is negative seven');
  })
  test( "equal test", function() {
    equal( 0, 0, "Zero; equal succeeds" );
    equal( "", 0, "Empty, Zero; equal succeeds" );
    equal( "", "", "Empty, Empty; equal succeeds" );
    equal( 0, 0, "Zero, Zero; equal succeeds" );

    equal( "three", 3, "Three, 3; equal fails" );
    equal( null, false, "null, false; equal fails" );
  });
  test( "strictEqual test", function() {
      strictEqual( 1, 1, "1 and 1 are the same value and type" );
  });

For this piece of code:

  // your applications custom code
  function addValues( a, b ) {
      return a + b;
  };

A Unit Testing in QUnit could be:

  // the QUnit test code
  test("test addValues(a, b) function", function() {
      equal(addValues( 1, 2), 3, "1 + 2 = 3" );
      equal(addValues( 1.75, 2), 3.75, "1.75 + 2 = 3.75" );
      notEqual(addValues( 1, 2), "3", "1 + 2 != '3' as a String");
  });

More info:

###Mocha

Mocha y Jasmine are probably the most popular frameworks. In many cases Jasmine is used to test JS in the client side, and Mocha is used to test JS in the server side (node.js)

##4.- Testing Runners

Grunt/Gulp/NPM

We can launch the tests from the console with any of the task runners. Thse tests can be launched in a "headless browser" (PhantomJS) or in real browsers (Karma)

HTML (Jasmine)

We can launch the tests directly from the browser (Jasmine provides its own stand-alone runner to launch the tests)

Karma

Karma is a "test runner" we can use to automatize the launch of our tests (written in Jasmine, Mocha o QUnit).

It allow us to execute automatically all our tests in different browsers. Check it in action

It also allow us to integrate our tests with continous integration tools like Jenkins

##5.- E2E testing frameworks

We can raise the test of our project to a higher level and do the so-called functional tests

These test emulte the behaviour of the final user and also test the expected behaviour of our product (no matter the internal code)

  describe('angularjs homepage todo list', function() {
    it('should add a todo', function() {
      browser.get('https://angularjs.org');

      element(by.model('todoList.todoText')).sendKeys('write first protractor test');
      element(by.css('[value="add"]')).click();

      var todoList = element.all(by.repeater('todo in todoList.todos'));
      expect(todoList.count()).toEqual(3);
      expect(todoList.get(2).getText()).toEqual('write first protractor test');

      // You wrote your first test, cross it off the list
      todoList.get(2).element(by.css('input')).click();
      var completedAmount = element.all(by.css('.done-true'));
      expect(completedAmount.count()).toEqual(2);
    });
  });

We can write and launch these tests with Protractor

Some other tools we can use to launch these tests are Selenium and WebDriver