Angular推出有好几年的时候了, 跟其他的MV*框架相比, 它的双向绑定,无须显式声明Model,模块管理,依赖注入等特点都给Web应用开发带来了极大的便利, 另外, 借助于它众多强大的原生directive, 我们几乎可以避免麻烦的DOM操作了, 除了这些, Angular还有一个很大的亮点, 那是高度的可测试性.
  的Web开发已经不同往日, 更多的交互与逻辑都需要在前端完成, 有时候, 前端的代码量甚至在后端之上. 怎么去保证如此多的前端逻辑不被破坏, 依赖于功能测试? 这显示不现实, 功能测试很耗时, 而且它的创建成本较高, 所以通常只用它来覆盖基本的那部分逻辑, 另一方面, 功能测试是依赖于流程的, 如果你想验证购买页面上的某个前端逻辑, 那么你不得不一路从产品详情页面老老实实点过来, 反馈时间太长了, 可能你要等一分多钟才知道某个功能出错了, 我们自然不想把宝贵的开发时间浪费在等待上.
  我在过去一段比较长的时候里都在项目上使用Angular, 在感受到Angular带来的便利的同时, 也饱受了Angular测试的折磨, 因为我一直觉得Angular的单元测试很难写, 跟JUnit + Mockito比起来, Angular代码的单元测试真是感觉写起来不得心应手, 更别说用TDD的方式来驱动开发.
  我一直在思考为什么Angular社区说Angular的测试性很高, 但是在项目上实现用起来却是另一番境地. 经过分析项目上的代码, 我觉得要想驱动测试开发Angular代码, 那么其实是对你的Angular代码提出了比较高的要求, 你要遵循Angular的风格来开发你的应用, 只有你了解了其中的思想, 你的测试写起来才会轻松.
  如果你已经使用Angular有一段时间了, 但是还没有读过这篇文章, 那么我强烈推荐你去读一下:Thinking in Angular
  先来看一看怎么样的Angular代码才是苗正根红的Angular代码.
  避免使用任何的DOM操作
  像DOM操作这样的脏活累活都应该交给Angular的原生directive去做, 我们的Angular代码应该只处理与DOM无关的业务逻辑. 来看一个简单的例子, 我们想创建一个简单的邮箱地址验证的directive, 它要实现的功能是, 当焦点从邮箱地址输入框移出的时候, 对输入框中的邮箱地址进行验证, 如果验证失败, 则向输入框添加一个样式表示输入的地址不合法, 比较糟糕的实现可能是这样的
  <label for=“email”>Your email:</label>
  <input id=“email” type=“text” ng-model=“email” validate-on-blur/>
  angular.module(“TheNG”, [])
  .directive(“validateOnBlur”, function () {
  return {
  link: function (scope, element, attrs) {
  var validate = function (email) {
  return email.indexOf(‘@gmail.com’) !== –1;
  };
  element.on(‘blur’, function () {
  if (!validate(scope.email)) {
  element.addClass(‘error-box’);
  }
  });
  }
  };
  }
  );
  上面的代码应该可以满足我们的要求(验证逻辑因为不是我们关注的重点, 所以并不完善), 而且这个directive实现起来也挺简单的, 但是现在让我们一起来分析一下为什么我们认为这种写法是比较糟糕的.
  简单的办法是在你的directive里面去找所有与DOM操作相关的代码.
  首先看到的是on()这个事件监听器. 完全没有必要自己去监听发生在被directive修饰的元素上的事件, angular有一整套的原生directive来干这个事情, 这里正确的做法应该是使用ng-blur来处理blur事件.
  下一个有问题的地方是addClass(), angular除了提供了事件监听相关的directive外, 也提供了操作元素本身属性的directive,ng-class可以用来替换addClass()方法.
  按照这个思路修改后的代码:
  <label for=“email”>Your email:</label>
  <input id=“email” type=“text” ng-model=“email” ng-blur=“validate()” ng-class=“{‘error-box': !isValid}” validate-on-blur/>
  angular.module(“TheNG”, [])
  .directive(“validateOnBlur”, function () {
  return {
  link: function (scope, element, attrs) {
  scope.isValid = true;
  scope.validate = function () {
  scope.isValid = scope.email.indexOf(‘@gmail.com’) !== –1;
  };
  }
  };
  }
  );
  比较一下这两个版本的实现, 是不是修改后的版本更简短, 更容易理解一些. 在新的版本里面, 我们只处理了业务逻辑, 即判断一个邮箱地址是否合法, 至于何时触发验证, 验证失败或成功之后应该有怎样的样式, 我们都统统交给了angular原生directive去处理了.
  从测试的角度来看, 如果想给第一个版本的实现写单元测试, 那么要准备和验证的东西都很多, 我们需要设法去触发对应元素的blur事件, 然后再验证这个元素上是否添加了error-box这个class, 根据我的经验, 有时候为了验证这些DOM更新, 你还不得不创建真实的DOM结构添加到DOM tree上去, 又增加了一部分工作量.
  而版本二简单多了, 只定义了一个Model值isValid来标识当前的邮箱地址是否合法,validate()方法会在每次失焦之后自动执行, 要为它添加单元测试, 则只需要调用一下它的validate()方法, 然后验证isValid的值可以了. SO EASY!~