技能樹之旅: 從模組分離到測試

Phodal發表於2015-03-03

技能樹之旅一: 從模組分離到測試

在之前說到

奮鬥了近半個月後,將fork的程式碼讀懂、重構、升級版本、調整,新增新功能、新增測試、新增CI、新增分享之後,終於almost finish。

今天就來說說是怎樣做的。

Github專案組成

以之前造的Lettuce為例,裡面有:

  • 程式碼質量(Code Climate)
  • CI狀態(Travis CI)
  • 測試覆蓋率(96%)
  • 自動化測試(npm test)
  • 文件

按照Web Developer路線圖來說,我們還需要有:

  • 版本管理
  • 自動部署

等等。

Skillock模組化

在SkillTree的原始碼裡,大致分為三部分:

  • namespace函式: 故名思意
  • Calculator也就是TalentTree,主要負責解析、生成url,頭像,依賴等等
  • Skill 主要是tips部分。

而這一些都在一個js裡,對於一個庫來說,是一件好事,但是對於一個專案來說,並非如此。

依賴的庫有

  • jQuery
  • Knockout

好在Knockout可以用Require.js進行管理,於是,使用了Require.js進行管理:

 <script type="text/javascript" data-main="app/scripts/main.js" src="app/lib/require.js"></script>

main.js配置如下:

require.config({
  baseUrl: 'app',
  paths:{
    jquery: 'lib/jquery',
    json: 'lib/json',
    text: 'lib/text'
  }
});

require(['scripts/ko-bindings']);

require(['lib/knockout', 'scripts/TalentTree', 'json!data/web.json'], function(ko, TalentTree, TalentData) {
  'use strict';
  var vm = new TalentTree(TalentData);
  ko.applyBindings(vm);
});

text、json外掛主要是用於處理web.json,即用json來處理技能,於是不同的類到了不同的js檔案。

.
|____Book.js
|____Doc.js
|____ko-bindings.js
|____Link.js
|____main.js
|____Skill.js
|____TalentTree.js
|____Utils.js

加上了後來的推薦閱讀書籍等等。而Book和Link都是繼承自Doc。

define(['scripts/Doc'], function(Doc) {
  'use strict';
  function Book(_e) {
    Doc.apply(this, arguments);
  }
  Book.prototype = new Doc();

  return Book;
});

而這裡便是後面對其進行重構的內容。Doc類則是Skillock中類的一個縮影

define([], function() {
  'use strict';
  var Doc = function (_e) {
    var e = _e || {};
    var self = this;

    self.label = e.label || (e.url || 'Learn more');
    self.url = e.url || 'javascript:void(0)';
  };

  return Doc;
});

或者說這是一個AMD的Class應該有的樣子。考慮到this的隱性繫結,作者用了self=this來避免這個問題。最後Return了這個物件,我們在呼叫的就需要new一個。大部分在程式碼中返回的都是物件,除了在Utils類裡面返回的是函式:

return {
    getSkillsByHash: getSkillsByHash,
    getSkillById: getSkillById,                
    prettyJoin: prettyJoin
};

當然函式也是一個物件。

Skillock測試

自動化測試

一直習慣用Travis CI,於是也繼續用Travis Ci,.travis.yml配置如下所示:

language: node_js
node_js:
  - "0.10"

notifications:
  email: false

branches:
  only:
    - gh-pages

使用gh-pages的原因是,我們一push程式碼的時候,就可以自動測試、部署等等,好處一堆堆的。

接著我們需要在package.json裡面新增指令碼

"scripts": {
    "test": "mocha"
  }

這樣當我們push程式碼的時候便會自動跑所有的測試。因為mocha的主要配置是用mocha.opts,所以我們還需要配置一下mocha.opts

--reporter spec
--ui bdd
--growl
--colors
test/spec      

最後的test/spec是指定測試的目錄。

Jshint

JSLint定義了一組編碼約定,這比ECMA定義的語言更為嚴格。這些編碼約定汲取了多年來的豐富編碼經驗,並以一條年代久遠的程式設計原則 作為宗旨:能做並不意味著應該做。JSLint會對它認為有的編碼實踐加標誌,另外還會指出哪些是明顯的錯誤,從而促使你養成好的 JavaScript編碼習慣。

當我們的js寫得不合理的時候,這時測試就無法通過:

line 5   col 25   A constructor name should start with an uppercase letter.
line 21  col 62   Strings must use singlequote.

這是一種驅動寫出更規範js的方法。

Mocha

Mocha 是一個優秀的JS測試框架,支援TDD/BDD,結合 should.js/expect/chai/better-assert,能輕鬆構建各種風格的測試用例。

最後的效果如下所示:

Book,Link
  Book Test
    ✓ should return book label & url
  Link Test
    ✓ should return link label & url

測試用例

簡單地看一下Book的測試:

/* global describe, it */

var requirejs = require("requirejs");
var assert = require("assert");
var should = require("should");
requirejs.config({
  baseUrl: 'app/',
  nodeRequire: require
});

describe('Book,Link', function () {
  var Book, Link;
  before(function (done) {
    requirejs(['scripts/Book'、], function (Book_Class) {
      Book = Book_Class;
      done();
    });
  });

  describe('Book Test', function () {
    it('should return book label & url', function () {
      var book_name = 'Head First HTML與CSS';
      var url = 'http://www.phodal.com';
      var books = {
        label: book_name,
        url: url
      };

      var _book = new Book(books);
      _book.label.should.equal(book_name);
      _book.url.should.equal(url);
    });
  });
});

因為我們用require.js來管理瀏覽器端,在後臺寫測試來測試的時候,我們也需要用他來管理我們的依賴,這也就是為什麼這個測試這麼從的原因,多數情況下一個測試類似於這樣子的。(用Jasmine似乎會是一個更好的主意,但是用習慣Jasmine了)

  describe('Book Test', function () {
    it('should return book label & url', function () {
      var book_name = 'Head First HTML與CSS';
      var url = 'http://www.phodal.com';
      var books = {
        label: book_name,
        url: url
      };

      var _book = new Book(books);
      _book.label.should.equal(book_name);
      _book.url.should.equal(url);
    });
  });

最後的斷言,也算是測試的核心,保證測試是有用的。

結束語(小廣告)

在最開始的時候想的是自己寫技能樹,直至在github上看到https://github.com/352Media/skilltree,就想著基於skilltree做一個,試著翻譯了一下,加了點功能。最後沒有避免被罵抄襲,最後想想還是自己寫一個吧:https://github.com/phodal/sherlock。叫Sherlock的原因是,原來是打算叫shelflock,柯南道爾在書中寫道:

人的大腦如同一間空空的閣樓,要有選擇地把一些傢俱裝進去。

基於D3.js,可以動態生成Url,而技能樹則會基於:https://github.com/phodal/awesome-developer

希望自己看過的書、走過的路可以給大家提供幫助

I'm Phodal.

相關文章