编写可测试的Javascript代码(2):从反模式进行重构

这是介绍“如何编写可测试的Javascript UI代码”两篇文章中的第二篇。

在第一篇文章反模式及其它们的解决方案中用一个示例应用程序引入了几个常见的,可避免的,约束了可测试性的反模式,并且解释了为什么这些常见的做法是反模式的以及如何修复他们。

这篇文章来继续重构原来的应用,以使得它的代码更容易阅读,更容易被复用以及更容易进行测试。一旦重构完成,测试就要开始:创建测试工具,开发XHR模拟,最后,添加一个完整的单元测试用例。

使用最佳实践来编写可测试的UI代码

在第一篇文章“反模式及其解决方案”中,列出了几个使得UI代码可测试的最佳实践:
1.外链所有的Javascript;
2.提供公共接口;
3.使用可实例化的对象;
4.减少嵌套回调;
5.将DOM事件处理程序与事件处理函数相分离;
6.当异步函数完成的时候通知订阅者;
7.测试完成后清除没用的对象;
8.在XHR请求中添加模拟对象;
9.把应用初始化分离成自己的模块。
用这个清单作为指引,原来的应用程序被会完全重构,并能达到我们对代码进行单元测试的目的。

从HTML开始–外链所有的脚本

原来内联在HTML文件中的Javascript代码已经被放在了外部并且被放置于两个文件中:authentication-form.js 和start.js。原来的大部分的逻辑放于authentication-form.js模块中,应用的初始化则在start.js中进行。

引用自index.html

拥有可选的公共接口的逻辑封装模块

AuthenticationForm是一个公共的可获取的模块,该模块比较简洁地封装了大部分的原始逻辑。AuthenticationForm提供了一个公共的接口,通过该接口其功能可以被测试。

一个公共接口–引用自authentication-form.js

使用可初始化的对象

原来的form表单例子没有可实例化的部分,这也就意味着它的代码只能运行一次。这样的话有效的单元测试几乎是不可能的。重构了的AuthenticationForm是一个原型对象,使用Object.create来创建新的实例。

可实例化的对象-引用自authentication-form.js

减少嵌套回调的使用

重构的AuthenticationForm从原来的的深层嵌套回调(导致代码成金字塔状)抽取逻辑形成四个公共的可以获取得到的函数。这些函数中的两个被用来提供给对象初始化和销毁,其余的两个用于测试接口。

去除金字塔–引用自authentication-form.js

将DOM事件处理程序从事件行为中分离出来

将DOM事件处理程序从事件行为中分离出来有助于代码的重用和可测试。

将DOM事件处理程序从事件行为中分离出来–引用自authentication-form.js

在异步函数中使用回调(或者其他的通知机制)

AuthenticationForm的测试接口中的两个函数,submitForm和checkAuthentication是异步的。当所有处理程序都完成的时候他们都接受一个函数进行回调。

有回调函数的异步回调–引用自authentication-form.js

将没用的对象处理掉

单元测试应该独立的进行。任何的状态,包括附属的DOM事件处理器,在测试的时候必须被重置。

移除附加的DOM事件处理程序–引用自authentication-form.js

将应用的初始化逻辑分离出一个单独的(初始化)模块

start.js是一个自调用的函数,它在多有的js文件都下载完成后执行。因为我们的应用很简单,只需要很少的初始化代码–一个AuthenticationForm实例被创建并初始化。

start.js

在这一点上,原来的整个应用程序被重构并且重新实现了。用户应该能看到在功能上并没有做改变,纯粹是代码结构的修改。

如何进行单元测试?

尽管当前我们的代码是可测试的,一篇关于单元测试的文章却没有写任何相关的测试代码!有几个高质量的测试框架,在这个例子中我们使用QUnit。

首先,我们需要一个测试工具。一个测试工具由一个模拟DOM和Javascript代码组成。模拟DOM由测试中要使用的元素组成,通常是类似于form或者你要检测可见性的元素这些东西。为了避免测试交叉污染,在每一个单元测试之后都将DOM元素进行重置。QUnit期望模拟元素包含在id为#qunit-fixture的元素中。

Javascript代码包含一个单元测试运行器,要被测试的代码,独立的模拟以及对他们自己的一些测试。

测试工具–引用自tests/index.html

书写XHR模拟

XHR请求需要依赖于服务端,从前端发起的请求必须被服务端响应否则应用什么都干不了。用真正的XHR请求进行测试意味着服务端必须做好准备,这会严重阻碍前后端并行开发。

与其发起真正的XHR请求,不如使用一个模拟的请求来做。模拟对象是一些替代对象–可以在测试中进行精确地控制。一个模拟对象必须实现用户要使用的所有功能。幸运的是,XHR模拟(也叫AjaxMock)只需要实现所有jQuery.ajax功能的很小的一部分即可。这个单独的模拟功能提供了整合所有服务端响应的能力。几个额外的函数被加进来辅助单元测试。

AjaxMock接口

完成一些测试!

现在,测试工具和XHR模拟都已经准备好了,我们可以写一些单元测试了!测试包含6个独立的测试。每个测试都会实例化一个新的AuthenticationForm对象和XHR模拟。XHR模拟可以为每一个可能的后端响应编写测试。

总结

花了一些时间,但是我们达到了我们的目的。我们的代码易于阅读,易于重用,并且有一个完整的测试套件。

编写可测试的代码通常是一个挑战,但是你一旦适应,基础的部分还是很容易的。在开始一行代码之前,你要问你自己“我要如何来对代码进行测试?”。这个简单的问题最终将节省大量时间和并在你重构或添加新功能的时候给你信心。

所有的代码在Github上可以获取:https://github.com/shane-tomlinson/shanetomlinson.com/tree/master/2013-jan-writing-testable-ui-javascript

如果你有任何问题,欢迎发表评论。

最终产品

index.html

authentication-form.js

start.js

tests/index.html

tests/ajax-mock.js

tests/authentication-form.js

打赏支持我翻译更多好文章,谢谢!

打赏译者

打赏支持我翻译更多好文章,谢谢!

任选一种支付方式

2 收藏 评论

关于作者:abell123

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部