[Sencha Ext JS 4] Unit Testing với Jasmine

Jasmine là một BDD (Behavior Driven Development) test framework có thể tích hợp vào nhiều môi trường khác nhau. Trong bài viết trước, tôi có nhắc đến Jasmine cùng với khái niệm về TDD. Thực tế thì BDD là một bản mở rộng của TDD mà thôi. Bạn có thể xem thảo luận về BDD và TTD tại đây. Về Unit testing, bạn có thể tìm hiểu qua rất nhiều các bài viết hoặc ebook nên tôi không đi vào quá chi tiết việc trình bày các khái niệm về unit testing. Quan điểm của tôi về unit testing như sau:

  • Unit testing để làm rõ và kiểm tra tính đúng đắn của specs
  • Unit testing cho thấy khả năng chia nhỏ các feature mà bạn đang hiện thực nó
  • Unit testing không thích hợp cho test UI, tốt nhất là áp dụng unit testing cho logic của ứng dụng. Bạn đặt logic của ứng dụng ở đâu (Model ? View ? ) thì hãy áp dụng unit testing ở đó.

Với ExtJS, hướng dẫn về unit testing được trình bày tương đối đơn giản và ngắn gọn, bạn nên đọc qua trước khi bắt đầu viết unit testing.  Bài viết sử dụng ứng dụng FlickrSearch  mà tôi đã trình bày từ trước, bạn có thể tìm hiều qua ứng dụng FlickrSearch. Phần trình bày về Jasmine sẽ tâp trung vào việc viết test cho 2 module Search Photo và màn hình Login. Trường hợp màn hình login là một thử nghiệm vì tôi không nghĩ mình sẽ viết unit testing cho phần UI như quan điểm trình bày ở trên.

Cài đặt Jasmine

Tôi sử dụng phiên bản Jasmine 1.3.1 – là phiên bản mới nhất tại thời điểm này. Bạn có thể thấy workspace của tôi khác một chút so với các ví dụ của ExtJS. Đơn giản là tôi thực hiện unit testing cho chính source code mà tôi đang viết.

jasmine workspace

Những module được đánh dấu là những phần thuộc về unit test. Như vậy bạn cần phải copy jasmine 1.3.1 vào project của mình. Folder specs là nơi đặt các test-case, app-test.js và run-test/html là các file để khởi động ứng dụng test.

app-test.js

File này khởi tạo môi trường chạy của jasmine cũng như khởi tạo các report cho các test run

Ext.Loader.setConfig({enabled:true});
Ext.require('Ext.app.Application');

var Application = null;
Ext.onReady(function() {
      Application = Ext.create('Ext.app.Application', {
      name: 'FlickrSearch',
      appFolder: 'app',
      controllers: ['ScreensController'],
      launch: function() {
         var htmlReporter = new jasmine.HtmlReporter();
         jasmine.getEnv().addReporter(htmlReporter);
         jasmine.getEnv().execute();
      }
 });
});

Test suite cho PhotoStore

Một test-suite bao gồm nhiều test-case, trong trưởng hợp này tôi tạo test-suite cho PhotoStore và định nghĩa một số test-case trong đó.

describe("Photo", function() {
    var controller = null;
    var store = null;
    beforeEach(function() {
        if (!controller) {
           controller = Application.getController("ScreensController");
        }
        if (!store) {
           store = controller.getStore('PhotoStore');
        }
   });
});

beforeEach (cùng với afterEach) là các hàm global – những hàm này sẽ được gọi trước (beforeEach) và sau (afterEach) mỗi test-case. Các test-case có cùng điều kiện khởi tạo và kết thúc có thể sử dụng các hàm này để khời tạo cũng như kết thúc test-case. Mục đích là để tránh lập lại các đoạn code dành cho khời tạo, kết thúc …  Trong trường hợp trên, mỗi test-case cần khởi tạo lại store và controller, nếu đặt các đoạn code này trong từng test-case sẽ dẫn đến việc các đoạn code này bị lập lại – bạn sẽ vi phạm nguyên tắc DRY – Don’t Repeat Yourself

Một số test-case cho PhotoStore

Kiểm tra việc khởi tạo controller

it("controller must be defined",function(){
 expect(controller).toBeDefined();
 });

Kiểm tra việc khởi tạo store

it("Store must be definded", function(){
 expect(store).toBeDefined();
 });

PhotoStore không phải là 1 store autoLoad, store này được kích hoạt thông qua việc gọi hàm load nên trong trường hợp này, thuộc tính autoLoad phải là false.

it("Store should not auto load",function(){
 expect(store.autoLoad).toEqual(false);
 });

test-case dưới đây minh họa việc test bất đồng bộ – store thực hiện một request tới server và chờ server response về . Giả định là request này sẽ phải thực hiện trong vòng 4 giây và nếu quá thời gian này, test -case sẽ fail

it("sets up latency configuration and receives latency", function() {
 runs(function() {
 store.load({params: { text: 'red' }});
 }, "an asynchronous method");
waitsFor(function() {
 return store.isLoading() == false;
 }, "store loading complete", 4000);
 });

Sau khi thực hiện request đến server và có response trong vòng 4 giây, kiểm tra kết quả store có dữ liệu hay không

it("Store should have photos", function() {
 expect(store.getCount()).toBeGreaterThan(0);
 });

Unit-test với UI

Theo quan điểm của tôi, UI có thể quá phức tạp để thực hiện unit-test. Với UI, nên sử dụng các phương pháp automation test sẽ có hiệu quả hơn ? Bạn có thể tham khảo thêm test-suite “authentication” mà tôi dùng để test UI. Kết quả chạy có thể thấy như sau

jasmine

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s