使用Karma测试AngularJS代码

###前言
这是一篇难产的博文,从去年写到了今年……

七月底到目前,已经使用了AngularJS开发了两三个项目。与之前使用的jQuery相比,Angualr非常强大,减少了一大堆的代码,印象最深的是它的双向数据绑定,一旦建立双向绑定,用户的任何变量操作都能立马同步到它绑定的变量,页面上的效果是这个值实时更新。这就意味着,当我们想要与后台服务器交互的时候,我们节省了收集数据的步骤,只需把绑定的对象放在参数里就行。

然而,AngularJS的强大不止如此。AngularJS的设计目标包括如下两点:

  • 将应用逻辑与对DOM的操作解耦。这会提高代码的可测试性。
  • 将应用程序的测试看的跟应用程序的编写一样重要。代码的构成方式对测试的难度有巨大的影响。

没错,AngularJS最强大的功能之一就是它的高可测试性。
我们在浏览AngularJS的官方文档的时候总能看到每个指令后面都会有一个测试代码片段。

###开始之前

下面我将编写一个简单的网页应用,将联系人存储到本地存储(LocalStorage)中。编写这个应用时,我将践行测试驱动开发(TDD)的精神。同时,路由导航方面,我不会用angular-router,而是使用ui-router。

###搭建项目
bower搭建项目框架:

mkdir karmaDmo
bower init
bower install bootstrap --save-dev
bower install angularjs --save-dev
bower install ui-router --save-dev
bower install angular-mocks --save-dev

初始化karma配置以及karma的使用,请参考我之前的文章 前端测试环境Karma简介

###编写测试用例

Karma的测试用例语法是Jasmine

describe('app.services test', function () {
  var service;
  beforeEach(function () {
    //clear localStorage,inject the service before running test
    if(window.localStorage) {
      window.localStorage.clear();
    }
    module('app.services');

    inject(function (storageService) {
      service = storageService;
    });
  });

  it('should be a string', function () {
    expect(angular.isString(service.storageKey)).toBeTruthy();
  });

  it('should be a object', function () {
    expect(angular.isObject(service.getInstance())).toBeTruthy();
  });

  it('should be an array', function () {
    expect(angular.isArray(service.getAll())).toBeTruthy();
  });

  it('should be an empty array', function () {
    expect(service.getAll().length).toEqual(0);
  });

  it('should be one item in the array', function () {
    var item = {
      name: 'Arthur',
      mobile: '+86-13452121990'
    };
    service.save(item);
    expect(service.getAll().length).toEqual(1);
  });
});

###编写实现代码

启动Karma测试环境,然后编辑本app的业务逻辑:

angular.module('app.services', [])
  .factory('storageService', function () {
    var factory = {
      storageKey: 'contactApp',
      getInstance: function() {
        if(window.localStorage) {
          var storage = window.localStorage.getItem(factory.storageKey) || { author: 'Arthur', description: 'save contacts to localStorage', data: []};
          return angular.fromJson(storage);
        }else{
          return;
        }
      },
      saveInstance: function(instance){
        var string = angular.toJson(instance);
        window.localStorage.setItem(factory.storageKey, string);
      },
      getAll: function(){
        var result = null,
        instance = factory.getInstance();
        if(instance) {
          result = instance.data || [];
        }else{
          result = [];
        }
        return result;
      },
      save: function(item){
        if(item.key) {
          factory.update(item);
        }else{
          factory.add(item);
        }
      },
      add: function(item){
        var instance = factory.getInstance();
        item.key = new Date().getTime();
        instance.data.push(item);
        factory.saveInstance(instance);
      },
      update: function(item){
        var instance = factory.getInstance(),
            datas = instance.data;
        for(var i in datas) {
          if(datas[i].key === item.key) {
            datas[i] = item;
            break;
          }
        };
        factory.saveInstance(instance);
      }

    };
    return factory;
  });

编写完毕后,Karma监控台的所有所有测试用例都亮绿灯的话,表明业务逻辑代码都是OK的。

本文章例子的源代码都发布到Github上,戳此查看

###一些思考
对于测试驱动开发的优劣点讨论这里不深入,但是有一点我记得很深刻。在最近的一个项目开发过程中,出现了一个bug让我们非常诧异的,因为之前的某个版本是正常的,但是在那个时候,它出问题了。我一位同事废了好大劲才发现这个bug是因为修改旧的bug才导致出现新的bug。于是我就思考,如果当时也运行着一套类似Karma这样的测试环境,我们肯定能避免这样子的问题出现的。

###参考

  1. Karma官网
  2. 浅谈测试驱动开发(TDD)