模块分离与测试

在之前说到

奋斗了近半个月后,将fork的代码读懂、重构、升级版本、调整,添加新功能、添加测试、添加CI、添加分享之后,终于almost finish。

今天就来说说是怎样做的。

以之前造的Lettuce为例,里面有:

  • 代码质量(Code Climate)
  • CI状态(Travis CI)
  • 测试覆盖率(96%)
  • 自动化测试(npm test)
  • 文档

按照Web Developer路线图来说,我们还需要有:

  • 版本管理
  • 自动部署

等等。

代码模块化

在SkillTree的源码里,大致分为三部分:

  • namespace函数: 顾名思义
  • Calculator也就是TalentTree,主要负责解析、生成url,头像,依赖等等
  • Skill 主要是tips部分。

而这一些都在一个js里,对于一个库来说,是一件好事,但是对于一个项目来说,并非如此。

依赖的库有

  • jQuery
  • Knockout

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

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

main.js配置如下:

  1. require.config({
  2. baseUrl: 'app',
  3. paths:{
  4. jquery: 'lib/jquery',
  5. json: 'lib/json',
  6. text: 'lib/text'
  7. }
  8. });
  9. require(['scripts/ko-bindings']);
  10. require(['lib/knockout', 'scripts/TalentTree', 'json!data/web.json'], function(ko, TalentTree, TalentData) {
  11. 'use strict';
  12. var vm = new TalentTree(TalentData);
  13. ko.applyBindings(vm);
  14. });

text、json插件主要是用于处理web.json,即用json来处理技能,于是不同的类到了不同的js文件。

  1. .
  2. |____Book.js
  3. |____Doc.js
  4. |____ko-bindings.js
  5. |____Link.js
  6. |____main.js
  7. |____Skill.js
  8. |____TalentTree.js
  9. |____Utils.js

加上了后来的推荐阅读书籍等等。而Book和Link都是继承自Doc。

  1. define(['scripts/Doc'], function(Doc) {
  2. 'use strict';
  3. function Book(_e) {
  4. Doc.apply(this, arguments);
  5. }
  6. Book.prototype = new Doc();
  7. return Book;
  8. });

而这里便是后面对其进行重构的内容。Doc类则是Skillock中类的一个缩影

  1. define([], function() {
  2. 'use strict';
  3. var Doc = function (_e) {
  4. var e = _e || {};
  5. var self = this;
  6. self.label = e.label || (e.url || 'Learn more');
  7. self.url = e.url || 'javascript:void(0)';
  8. };
  9. return Doc;
  10. });

或者说这是一个AMD的Class应该有的样子。考虑到this的隐性绑定,作者用了self=this来避免这个问题。最后Return了这个对象,我们在调用的就需要new一个。大部分在代码中返回的都是对象,除了在Utils类里面返回的是函数:

  1. return {
  2. getSkillsByHash: getSkillsByHash,
  3. getSkillById: getSkillById,
  4. prettyJoin: prettyJoin
  5. };

当然函数也是一个对象。

自动化测试

一直习惯用Travis CI,于是也继续用Travis Ci,.travis.yml配置如下所示:

  1. language: node_js
  2. node_js:
  3. - "0.10"
  4. notifications:
  5. email: false
  6. branches:
  7. only:
  8. - gh-pages

使用gh-pages的原因是,我们一push代码的时候,就可以自动测试、部署等等,好处一堆堆的。

接着我们需要在package.json里面添加脚本

  1. "scripts": {
  2. "test": "mocha"
  3. }

这样当我们push代码的时候便会自动跑所有的测试。因为mocha的主要配置是用mocha.opts,所以我们还需要配置一下mocha.opts

  1. --reporter spec
  2. --ui bdd
  3. --growl
  4. --colors
  5. test/spec

最后的test/spec是指定测试的目录。

Jshint

JSLint定义了一组编码约定,这比ECMA定义的语言更为严格。这些编码约定汲取了多年来的丰富编码经验,并以一条年代久远的编程原则 作为宗旨:能做并不意味着应该做。JSLint会对它认为有的编码实践加标志,另外还会指出哪些是明显的错误,从而促使你养成好的 JavaScript编码习惯。

当我们的js写得不合理的时候,这时测试就无法通过:

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

这是一种驱动写出更规范js的方法。

Mocha

Mocha 是一个优秀的JS测试框架,支持TDD/BDD,结合 should.js/expect/chai/better-assert,能轻松构建各种风格的测试用例。

最后的效果如下所示:

  1. Book,Link
  2. Book Test
  3. should return book label & url
  4. Link Test
  5. should return link label & url

测试示例

简单地看一下Book的测试:

  1. /* global describe, it */
  2. var requirejs = require("requirejs");
  3. var assert = require("assert");
  4. var should = require("should");
  5. requirejs.config({
  6. baseUrl: 'app/',
  7. nodeRequire: require
  8. });
  9. describe('Book,Link', function () {
  10. var Book, Link;
  11. before(function (done) {
  12. requirejs(['scripts/Book'、], function (Book_Class) {
  13. Book = Book_Class;
  14. done();
  15. });
  16. });
  17. describe('Book Test', function () {
  18. it('should return book label & url', function () {
  19. var book_name = 'Head First HTML与CSS';
  20. var url = 'http://www.phodal.com';
  21. var books = {
  22. label: book_name,
  23. url: url
  24. };
  25. var _book = new Book(books);
  26. _book.label.should.equal(book_name);
  27. _book.url.should.equal(url);
  28. });
  29. });
  30. });

因为我们用require.js来管理浏览器端,在后台写测试来测试的时候,我们也需要用他来管理我们的依赖,这也就是为什么这个测试这么长的原因,多数情况下一个测试类似于这样子的。(用Jasmine似乎会是一个更好的主意,但是用习惯Jasmine了)

  1. describe('Book Test', function () {
  2. it('should return book label & url', function () {
  3. var book_name = 'Head First HTML与CSS';
  4. var url = 'http://www.phodal.com';
  5. var books = {
  6. label: book_name,
  7. url: url
  8. };
  9. var _book = new Book(books);
  10. _book.label.should.equal(book_name);
  11. _book.url.should.equal(url);
  12. });
  13. });

最后的断言,也算是测试的核心,保证测试是有用的。