无论我们使用和Node配合在一起的测试框架,例如Mocha或者Jasmine,还是在像PhantomJS这样的无头浏览器中运行依赖于DOM的测试,和以前相比,我们有更好的方式来对JavaScript进行单元测试。

然而,这并不意味着我们要测试的代码就像我们的工具那样容易!组织和编写易于测试的代码需要花费一些精力和并对其进行规划,但是在函数式编程的启发下,我们发现了一些模式,当我们需要测试我们的代码时,这些模式可以帮助我们避免那些“坑”。在这篇文章中,我们会查看一些有用的小贴士和模式,来帮助我们在JavaScript中编写可测试的代码。

保持业务逻辑和显示逻辑分离

对于基于JavaScript的浏览器应用程序来说,其中一项主要工作就是监听终端用户触发的DOM事件,然后通过运行一些业务逻辑并在页面上显示结果,以此对用户做出反馈。在建立DOM事件监听器的地方,有时会诱惑你编写一个匿名函数来完成所有这些工作。这样带来的问题是为了测试匿名函数,你不得不去模拟DOM事件。这样不仅会增加代码行数,而且会增加测试运行的时间。

与之相反,编写一个有名字的函数,然后将其传给事件处理器。通过这种方式,你可以直接针对这个有名字的函数编写测试用例,而不用去触发一个假的DOM事件。

这不仅仅可以应用到DOM上。在浏览器和Node上的很多API,都被设计成触发和监听事件,或者等待其它类型的异步工作完成。凭经验说,如果你编写了大量的匿名回调函数,那么你的代码可能不会容易被测试。

// hard to test

$('button').on('click', () => {

$.getJSON('/path/to/data')

.then(data => {

$('#my-list').html('results: ' + data.join(', '));

});

});

// testable; we can directly run fetchThings to see if it

// makes an AJAX request without having to trigger DOM

// events, and we can run showThings directly to see that it

// displays data in the DOM without doing an AJAX request

$('button').on('click', () => fetchThings(showThings));

function fetchThings(callback) {

$.getJSON('/path/to/data').then(callback);

}

function showThings(data) {

$('#my-list').html('results: ' + data.join(', '));

}

对异步代码使用回调或者Promise

在上述的示例代码中,我们经过重构的函数fetchThings会运行一个AJAX请求,以异步的方式完成了大部分工作。这意味着我们不能运行函数并测试它是否按照我们预期的那样运行,因为我们不知道它什么时候运行完。

解决这个问题最常见的方法,是向函数中传递一个回调函数作为参数,作为异步调用。这样,在你的单元测试中,你可以在传递的回调函数中运行一些断言。

另外一种常见并且越来越流行的组织异步代码方法,是使用Promise API的方式。幸运的是,$.ajax和其它大部分jQuery的异步函数已经返回了Promise对象,因此它已经涵盖了大部分常见的用例。

// 很难测试;我们不知道AJAX请求会运行多长时间

function fetchData() {

$.ajax({ url: '/path/to/data' });

}

//可测试的;我们传入一个回调函数,然后在其中运行断言

function fetchDataWithCallback(callback) {

$.ajax({

url: '/path/to/data',

success: callback,

});

}

//同样可测试的:在返回的Promise解析完后,我们可以运行断言

function fetchDataWithPromise() {

return $.ajax({ url: '/path/to/data' });

}

避免副作用

要编写那些使用参数并且返回值仅仅依赖那些参数的函数,就像将数字传入数学公式,然后取得结果。如果你的函数依赖于一些外部的状态(例如类实例的属性或者某些文件的内容),那么你在测试这个函数之前,就不得不去设置一些状态,在测试用例中需要更多的设置。你不得不去认为那些正在运行的代码不会修改同一个的状态。

同样,你需要避免编写那些会修改外部状态的函数,例如向文件写入内容或者向数据库保存数据。这会避免一些副作用,来影响你测试其他代码的能力。一般来说,最好是将副作用和代码控制在一起,让“表面积”尽可能小。对于类和对象实例来说,类方法的副作用应该被限制在被测试的类实例的范围内。

// 很难测试;我们不得不设置一个globalListOfCars对象和一个名为#list-of-models的DOM结构,然后才能测试这段代码

function processCarData() {

const models = globalListOfCars.map(car => car.model);

$('#list-of-models').html(models.join(', '));

}

// 容易测试;我们传递一个参数然后测试它的返回值,而不需要设置任何全局变量或者检查任何DOM结果

function buildModelsString(cars) {

const models = cars.map(car => car.model);

return models.join(',');

}

使用依赖注入

在函数中,有一种通用的模式,可以用来降低对外部状态的使用,这就是依赖注入 —— 将函数的所有外部需要都通过函数参数的方式传递给函数。

// 依赖于一个外部状态数据连接实例;很难测试

function updateRow(rowId, data) {

myGlobalDatabaseConnector.update(rowId, data);

}

// 将数据库连接实例作为参数传递给函数;很容易测试。

function updateRow(rowId, data, databaseConnector) {

databaseConnector.update(rowId, data);

}

使用依赖注入的一个主要好处,是你可以在单元测试中传入mock对象,这样就不会导致真的副作用(在这个例子中,就是更新数据库行),你只需要断言你的mock对象是按照期望的方式运行即可。

为每一个函数设置一个唯一的目的

将长函数分解成一系列小的、单一职责的函数。这样我们可以更容易的去测试每一个函数是否是正确的,而不再希望一个大函数在返回结果之前就正确的做了所有的事情。

在函数式编程中,将几个单一职责的函数拼在一起的行为称作“组合”。Underscore.js甚至有一个名为_.compose的函数,它将一个函数列表中的函数串在一起,将每一函数的返回结果作为输入传递给下一个函数。

// 很难测试

function createGreeting(name, location, age) {

let greeting;

if (location === 'Mexico') {

greeting = '!Hola';

} else {

greeting = 'Hello';

}

greeting += ' ' + name.toUpperCase() + '! ';

greeting += 'You are ' + age + ' years old.';

return greeting;

}

// 很容易测试

function getBeginning(location) {

if (location === 'Mexico') {

return '¡Hola';

} else {

return 'Hello';

}

}

function getMiddle(name) {

return ' ' + name.toUpperCase() + '! ';

}

function getEnd(age) {

return 'You are ' + age + ' years old.';

}

function createGreeting(name, location, age) {

return getBeginning(location) + getMiddle(name) + getEnd(age);

}

不要改变参数

在JavaScript中,数组和对象传递的是引用,而非值,因此它们是可变的。这意味着当你将对象或者数组作为参数传递给函数时,你的代码和使用你传递的对象或数组的函数,都有能力去修改内存中同一个数组或者对象。这意味着当你测试你自己的代码时,你必须信任所有你调用的函数中,没有任何函数会修改你的对象。每当你添加一些新的可以修改同一个对象的代码时,跟踪对象应该是什么样子就会变得越来越困难,从而更难去测试它们。

相反,当你有一个函数需要使用对象或者数组时,你应该在代码中对待对象或者数组就像它们是只读的。你可以根据需要创建新的对象或者数组,然后对齐填充。或者,使用Undersocre或者Lodash去对传入的对象或者数组做一个拷贝,然后再对齐进行操作。更好的选择是,使用一些像Immutable.js这样的工具,去创建只读的数据结构。

// 修改了传入的对象

function upperCaseLocation(customerInfo) {

customerInfo.location = customerInfo.location.toUpperCase();

return customerInfo;

}

// 返回了一个新的对象

function upperCaseLocation(customerInfo) {

return {

name: customerInfo.name,

location: customerInfo.location.toUpperCase(),

age: customerInfo.age

};

}

在编码之前先写测试

在编码之前先写单元测试的过程被称作测试驱动开发(TDD)。大量的开发者发现TDD非常有用。

通过先编写测试用例,你就强迫自己从使用你代码的开发者角度来考虑你要暴露的API,它还帮助你确保你只会编写足够的代码来满足测试用例的要求,而不要对解决方案“过度施工”,从而带来不必要的复杂性。

在实践中,TDD作为一条纪律,要覆盖所有的代码改动可能会比较困难。但是当它看上去值得尝试的时候,这就是一个很好的方式来保证你的所有代码都是可测试的。

总结

在编写和测试复杂的JavaScript应用的时候,我们都知道有一些很容易遇到的“陷阱”,但我希望通过这些贴士和提醒,可以让我们的代码尽量简单和函数化,我们可以做到让测试覆盖率很高,让整体的代码复杂性很低!

用java写穿越火线代码_编写可测试的 JavaSript 代码相关推荐

  1. 如何编写无法维护的代码_编写可维护的前端代码

    点击这里获得更好的阅读体验​github.com 以下是本人在团队内部分享的整理和补充,水平有限,如有错误,请不吝赐教. 大家好,我叫王力国,目前是 RPA 前端团队负责人,过去一年我们从零构建了 R ...

  2. java写渗透工具_常用渗透测试工具使用tips

    (7)sqlmap(python脚本学习下) 经典sql注入工具 (这种针对参数的工具,不知道是不是扫描方式有问题,还是怎么着,怎么才能抓几个包,或者把常用点的包抓出来) 抓几个sqlmap的包 sq ...

  3. 《编写可测试的JavaScript代码》——1.4 小结

    本节书摘来自异步社区<编写可测试的JavaScript代码>一书中的第1章,第1.4节,作者: [美]Mark Ethan Trostler 译者: 徐涛 更多章节内容可以访问云栖社区&q ...

  4. 编写可测试的JavaScript代码

    <编写可测试的JavaScript代码> 基本信息 作者: [美] Mark Ethan Trostler 托斯勒 著 译者: 徐涛 出版社:人民邮电出版社 ISBN:9787115373 ...

  5. 《编写可测试的JavaScript代码》——1.3 卓越的应用程序代码

    本节书摘来自异步社区<编写可测试的JavaScript代码>一书中的第1章,第1.3节,作者: [美]Mark Ethan Trostler 译者: 徐涛 更多章节内容可以访问云栖社区&q ...

  6. 用java写ods系统_基于数据库的代码自动生成工具,生成JavaBean、生成数据库文档、生成前后端代码等(TableGo v7.0.0版)...

    TableGo是基于数据库的代码自动生成工具,低代码编程技术的实现,可以零代码自动生成SpringBoot项目工程.生成JavaBean.生成前后端分离的CRUD代码.生成MyBaits的Mapper ...

  7. python代码编写工具_编写更好的Python代码的终极指南

    python代码编写工具 Despite its 尽管它 downsides, Python remains the king of today's programming world. Its ve ...

  8. Java FX8_第一篇_编写第一个Java FX Application

    所有的Java FX程序必须继承自Application类且必须覆写start( )方法,而start ( )方法的参数是一个stage(继承Application类且使用Stage类必须导入java ...

  9. junit编写测试代码_编写数据访问代码测试-不测试框架

    junit编写测试代码 当我们向数据访问代码编写测试时,是否应该测试其公共API的每种方法? 一开始听起来很自然. 毕竟,如果我们不测试所有内容,那么如何知道我们的代码可以按预期工作? 这个问题为我们 ...

最新文章

  1. 你有一张世界互联网大会的门票待领取!数字经济人才专场报名开启
  2. 简单工厂、工厂模式初学习
  3. css涟漪光圈扩散_CSS动画实例:圆的涟漪扩散
  4. ajax 加载partial view ,并且 附加validate验证
  5. Java基础学习(一)—方法
  6. JEPaas代码_((列表)输入字段值而改变值
  7. Silverlight学习笔记(一)——Silverlight够酷吗?
  8. 选择排序 冒泡排序 二分查找
  9. 推荐一个开源文本识别工具箱,实现 5 种实用经典算法
  10. H3C 模拟器 pc与sw直连 开启telnet
  11. 深度学习自学(二十六):人脸数据集
  12. RESTORE DATAFILE TO A NEW LOCATION
  13. 基于Lua语言的wireshark插件编写
  14. SQL Server 2016安装教程
  15. 竞品分析 | 不背单词、百词斩
  16. OpenSTF手机设备管理平台-------二次开发
  17. SEO(搜索引擎优化)简单说下关键词矩阵策略
  18. stock 工具收集
  19. 基于spring boot 的学生科研项目共享平台毕业设计源码271611
  20. 如何通过JMeter测试金仓数据库KingbaseES并搭建环境

热门文章

  1. GRACE数据的广义三角帽(TCH)计算不确定度
  2. python的sql注入
  3. LCD/LED/OLED/等离子显示器区别
  4. 什么情况下,需要做血液透析?
  5. 医疗行业:机房该如何监控,才更高效?
  6. QQ截图工具在win10放大的问题
  7. 【车载开发系列】诊断故障码DTC基本概念与定义
  8. 长度是指字节还是字符
  9. python报错:local variable ‘xxx‘ referenced before assignment
  10. proxmox VE超融合项目实践