编写可测试的JavaScript代码

一、可测试的JavaScript

A.现有技术

1.敏捷开发

①使用敏捷开发,并不一定意味着应用程序完成得更快且质量更高,敏捷开发最大的优势是它处理需求变更的方式。

②快速迭代和持续交互可以加快高质量软件的交付。

2.测试驱动开发

在编写代码之前先编写测试,这些测试提供了必须遵循预期功能的代码,编写测试失败后,接着开始编写代码,以便确保测试能够通过。保持测试领先于开发,永远不会有未被测试的代码。

3.行为驱动开发

它为开发人员和非开发人员提供了一种能用语言,用于描述正确的应用程序行为和模块行为,该通用语言是日常语言。

B.代码是让人用的

1.我们编写的代码不是让电脑用的,而是让人用的

2.为何要编写可测试的代码

可测试的代码更加容易测试,意味着它更加容易维护,易维护则意味着它有让人(包括自己)更加容易理解 ,更加容易维护,从而又使得测试变得更加容易

3.如果没有可测试的、可维护的以及可理解的代码,那它就是垃圾

4.什么是可测试的代码

什么是可测试:短小但也不太复杂的代码、完整的注释,以及检耦合。

什么是可维护:可以存在于一个完整的产品周期:产品从一个人转到另外一个人手里时,不需要部分或全部重写

什么是可理解:简单的、小型的且有注释的代码更加容易理解

5.如何编写可测试的代码:编写短小、最小依赖和最低复杂度的可隔离的代码块

二、复杂度

A.代码大小

可以让函数保持最小代码量的一个方法让命令(Command)和查询(Query)保持分离。命令函数表示做什么(do something),而查询函数则表示返回什么(return something)。也就是说,命令表示setter,查询表示getter。命令函数使用模(mock)进行测试,而查询函数使用桩(stub)进行测试。让这些概念保持分离,并提高可测试性,通过确保读写分离,可以实现良好的可伸缩性。

B.JSLint

http://www.jslint.com/

C.圈复杂度

1.圈复杂度是表示代码中独立现行路径的数量。换句话说,它是为锤炼所有的代码,需编写的单元测试的最小数量。

2.生成代码的圈复杂度可以使用像jsmeter这样简单的命令行工具

3.为了实现合理性和可维护性,保持较低的圈复杂度是一个好办法

4.圈复杂度高的代码通常是由很多if/then/else语句造成的,最简单的修复是将方法分解成更小的方法

5.使用jscheckstyle来计算圈复杂度

D.重用

1.减小代码大小的最好办法是减少编写的代码量。其理论是使用其他人维护的可用于生产环境的第三方(外部或内部的代码),这样就可以减少一大笔代码维护成本。

2.典型的应用程序由20%的通用组件和高达65%与具体领域有关的可重用组件构成。而程序的其余部分则是由具体应用系统相关的定制化代码和一些地方所使用到的实用程序构成。

①程序特定:我们自己编写的代码

②领域特定:在程序中使用的第三方模块

③领域独立:类似YUI这样的框架或Node.js

3.如果发现代码被编写了两遍,那就是时候将其提取到函数中了。

E.扇出

1.扇出(Fan-out)测量函数直接或间接依赖的模块或对象的数量。

2.扇出:

过程A的扇出是表示过程A的内部流程数量与过程A所更新的数据结构数量之和。

在该定义中,如下任意操作都算作一个内部流程(以方法B和C为例):

①如果A调用B;

②如果B调用A,并且A返回一个B随后 可以利用的值;

③如果C调用A和B,且A的返回值传递给B。

所以,将函数A所有的内部流程,加上A所更新的全局结构(相对于A外部),产生的数字就是函数A的扇出。

3.对于所有的函数 ,计算该扇出值和该值所对应的扇入值,将两数相乘,并进行平方计算,其结果数字 就是一个函数 的复杂度。(fan_in * fan_out)²

4.对于高复杂度的代码:

高扇入和扇出的代码,可能表示一个函数正在尝试做太多事情,应该避免

高扇入和扇出,可以判定出系统的压力点,维护这些函数将会非常困难,因为它们关联太多的系统其它部分

它们不够精细,

5.高扇出会带来的问题:代码更复杂、更难以理解 ,所有更难以测试;而且测试过程中,每个直接依赖必须要被模拟(mock或stub),所以会增加创建测试的复杂性;并且扇出象征着着紧耦合,会使函数和模块过于脆弱。

F.扇入

1.过程A的扇入是过程A的内部流程数量与欲从过程A中获取信息的数据结构数量之和。

G.耦合:六级耦合

1.内容耦合:内容耦合是最紧的耦合形式,包括在外部对象上调用方法或函数,或通过修改外部对象的属性直接改变对象状态。

2.公共耦合:如果两个对象都共享另外一个全局变量,则这两个对象就有公共耦合了。

3.控制耦合:该耦合基于标记或参数设置来控制外部对象。

4.印记耦合:通过向外部对象传递一个记录,而只使用该记录的一部分

5.数据耦合:发生在一个对象传递给另一个对象消息数据,而没有传递控制外部对象的参数时。

6.无耦合:任意两个对象之间的绝对零耦合。

*虽然不是正式耦合的一部分,实例化一个非单例全局对象的行为也是一种非常紧密的耦合,其耦合程度接近于内容耦合,但比公共耦合紧密。

H.耦合性度量

1.代码检查和代码审查是查找代码耦合的一个非常好的方法,而不是依靠工具来发现耦合性度量

I.依赖注入

1.注入和模拟是松散的关系,注入负责构造对象,并将对象注入到代码中;而模拟是在调用的时候替换对象或方法以便于测试。工厂化依赖,或手动将依赖注入到构造函数或方法调用中,有助于减少代码的复杂性,但也会增加一些开销:如果一个对象的依赖项需要注入,而另外一个对象此时则负责构建该对象。

2.依赖注入器可以为代码构建和注入完全成型的对象。

J.注释

1.对于可测试的JavaScript,所有即将要测试的函数或方法前面都有相应的注释。根据这些注释,我们(或其他人)可以知道如何进行测试以及测试什么内容。

2.YUIDoc和JSDoc可以将所有的注释转换为HTML。

3.Docco/Rocco,从代码中解析出Markdown风格的注释。

三、基于事件的架构

A.基于事件编程的好处

1.从核心上看,所有的应用程序都与消息传递有关。可能会发生紧耦合,因为代码需要另一个对象的引用 ,以便可以给对象发送消息或接收消息。

2.全局的依赖关系是很危险的:系统的任何部分接触它们,都使得BUG很难跟踪;如果我们有同名或类似的局部变量,一不小心就会改变这些全局依赖;由于无处不在的特性,也会导致数据封装出错,使得调试非常困难。JS全局变量的声明和使用很简单,并且宿主环境通常提供了多个全局变量、全局函数和全局对象。这意味着,将变量保存到全局作用域内必须要小心,因为全局作用域内已经有很多全局对象了。

3.基于事件的编程都可以归结为两个主要部分:调用和返回。将调用转换为参数化的事件,并返回一个参数化的回调。

B.事件集线器

1.事件背后的思想很简单:将方法注册到事件中心,指定其能够处理的某些事件。方法利用停线器独立的中央处理器,负责事件请求,并等待响应。

2.该架构发挥了JS函数的优势,鼓励使用最小依赖项的小型耦合代码。鼓励开发人员编写使用最小依赖项的小块代码,使用事件而不是方法调用,可以极大地提高可测试性和可维护性。

3.基于事件的架构帮助执行了MVC所倡导的关注点分离以及模块化,区别在于,基于事件的架构模型被打乱、消除或分离,这取决于我们如何看待这些模型。基于事件架构的数据并不是存储在对象中。没有任何修饰符,所有的内容都是私有的,与“外部”世界唯一的沟通就是通过基于事件的API。

C.测试基于事件的架构

1.基于事件架构的本质:注册事件监听,并且没有(或很少)对象被实例化

D.基于事件架构的说明

1.可伸缩性:事件集线器创造了超级单一故障点,如果集线器出现了故障,应该程序就宕机了

2.广播:使用广播将很多事件广播给所有的客户端可能会带来很多通信流量

3.运行时检测:编译器没有办法检查字符串形式的事件名称的拼写错误,强烈建议对事件名称使用枚举或散列,而不是在输入的时候一遍一遍检查

4.安全性

5.状态:通常是由Web服务器通过会话cookie,从Web服务器提供给业务模块的

四、单元测试

A.单元测试框架

1.测试框架最重要的部分是将测试聚合到测试套件和测试用例中。测试套件和测试用例是分散在很多文件中的,并且每个测试文件通常只包含单个模块的测试。最好的办法是将一个模块的所有测试都归类到一个单独的测试套件中。

2.断言是将期望值和实际值进行比较的实际应用。

B.开始编写测试

1.YUI test

https://github.com/zhangyue0503/html5js/blob/master/testablejs/1.html

C.编写好的单元测试

1.怎样编写一个好的单元测试?代码覆盖率。

2.隔离:单元测试应该只加载 所需测试的最小代码进行测试。任何额外的代码都可能会影响测试或被测试代码,而且还会产生问题。

3.范围:必须很小,一个完全隔离的方法可以让测试的范围尽可能地小。

4.在编码之前,利用测试驱动开发先编写单元测试,并不能避免函数所需要的注释。如果先编写测试用例,也可以用于规范函数 (或被测试代码)功能

5.正向测试:按正确的数据测试,首先要编写的单元测试,因为在构建负向测试和边界测试之前 ,它们提供了基本的预期功能。

6.负向测试:传入非期望的函数或该函数不想要的参数进行测试,确保被测试的函数能够处理它们。负向测试找到的BUG通常是难以对付的BUG。

7.代码覆盖率:是指一种度量方法,通常是指执行代码与非执行代码行数之间的百分比,是有效单元测试的另一个关键部分

D.真实场景测试

1.单元测试者可以利用模(mock)和桩(stub)提取依赖关系,mock用于命令,而sub用于查找

2.测试替身:描述的是使用sub或mock模拟依赖对象进行测试。

E.运行客户端JavaScript单元测试

1.PhantomJS

2.Selenium

F.运行服务器端JavaScript单元测试

1.jasmine

五、代码覆盖率

为代码覆盖率信息构建相应的JS文件,部署或练习这些文件,并把覆盖率结果推送并持久化到一个本地文件中,也可以将不同测试的覆盖率结果组合在一起,生成漂亮的html输出,或者仅仅为上游工具或报告获取相应的覆盖率数字和百分比

A.覆盖率基础理论

1.理论上讲,“覆盖”的代码行数越多,测试就越完整,然而,代码覆盖率和测试完整性之间的联系是很脆弱的。

B.代码覆盖率数据

1.代码覆盖率数据分为两部分,代码行的覆盖率和函数的覆盖率。

六、集成测试、性能测试、负载测试

A.集成测试

1.Selenium:通常需要在浏览器的同一个沙盒上运行大量的java代码以便运行测试,以及一个用于控制远程浏览器的客户端API,可以使用各种语言编写Selenium小夜曲。

2.CasperJS:提供了与Selenium类似的功能,但完全不限制环境。

B.性能测试

1.HAR文件:可用于查看的json格式对象,可以使用很多工具对其进行查看,要监控web应用程序的性能,需要生成应用程序概要的HAR文件,然后检查数据并发现问题。

C.负载测试

1.nodeload,(Apache Bench的node.js版本)

七、调试

A.浏览器内调试

1.firebug

2.chrome

B.Node.js调试

1.命令行调试器

node debug myscript.js

C.远程调试

1.chrome远程调试

2.PhantomJS

3.firefox远程调试:Crossfire扩展

D.移动调试

1.原生支持:Android4以及ios6以后

E.生产环境调试

1.源映像文件

八、自动化

A.自动化什么内容

自动化一切,任何不止一次操作的内容都应该自动化。

B.何时进行自动化

三个时机:编码阶段、构建阶段、部署阶段

原文发布于微信公众号 - 硬核项目经理(fullstackpm)

原文发表时间:2019-06-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券