WebAssembly:系统编程语言的逆袭

引子

Any application that can be written in JavaScript, will eventually be written in JavaScript. ——Atwood‘s Law

有人用 JavaScript 做语法词法解析,有人写了 x86 模拟器, 还有人用 JavaScript 写了可自举的 JavaScript 引擎。JavaScript 早已经在”重新发明一切”的路上一骑绝尘了,JavaScript 的流行也使它始终位于各大语言排行榜上的前列,这无疑是属于 JavaScript 程序yuan们最好的时代。

这并非是因为 JavaScript 是门优秀的语言 (恰恰相反),而是因为当今的世界是 Web 的世界,Web 的载体浏览器只会说 JavaScript。这难免使人眼红,王侯将相,世人无数次想要取代 JavaScript 的地位,目前为止的历史我们都看到了,无一不铩羽而归。求上不得,得其中,最新成果是大家(伙儿)齐心协力把 JavaScript 变成了新一代的汇编语言。请移步这里看大家的最新成果。

WebAssembly

去年 11 月 13 日,Mozilla 在其官方博客上发表了一篇文章,WebAssembly support now shipping in all major browsers,指出当今世界四大主流浏览器 Firefox,Chrome,Safari,Edge(排名分先后),都已经支持了名为 WebAssembly 的新技术,并回顾了一路走来的艰难历程。最后指出这是新时代的开端,大家一起欢呼吧。那么,WebAssembly 到底是啥?让我们发出发聋振聩的三连问:

可以吃吗?

请移步 WebAssembly 官网。官网解释如下:

WebAssembly or wasm is a new portable, size- and load-time-efficient format suitable for compilation to the web.

关键词:

  1. 可移植: WebAssembly 是一种可移植的二进制格式,它不依赖于具体的浏览器平台。
  2. 高效:WebAssembly 被设计为针对 Size 和 Load Time 进行优化的格式,可以在各个硬件平台上以 native speed 运行。
  3. 安全:WebAssembly 是运行在沙盒内的,甚至可以和当前的 JavaScript 虚拟机共享一套环境,并且也遵守浏览器各种跨域不跨域的规章制度。
  4. 开放:WebAssembly 是开放标准,不受某一家厂商控制(Oracle你听到了吗)。并且被设计为可以和 JavaScript API 和 Context 交互。

简而言之,WebAssembly 可以被看做是通过浏览器运行的某种高效的开放的二进制格式,并且可以和 JavaScript 环境互通。

WebAssembly 的目的是取代 JavaScript 吗?FAQ 这样回答:不,WebAssembly 是被设计来补充而不是替代 JavaScript。随着时间推移,越来越多的语言可以被编译为 WebAssembly,但是 JavaScript 还是作为 Web 唯一的动态语言而存在。

这样看来老二的位置摆得很正嘛。对于 WebAssembly, 笔者最看重的一点是作为开放标准的同时有粗大腿的支持 (M$ Google Apple Mozilla),这才是它有可能活下来的原因。问题是可以吃吗,答案当然是可以吃(佛系码农也可以不吃)。

怎么吃?

WebAssembly 同时存在一个二进制格式和一个文本的描述格式,这很像是机器语言和汇编语言的关系。这里我们用一个例子解释一下。

事实上,WebAssembly 可以被看作是运行在一个 structured stack virtual machine 里,懂行的朋友一眼就可以看出这和 Java bytecode 非常的像。所以大家不要以为 WebAssembly 是在重新发明 Flash 了,这货明明是在重新发明 Java Applet 啊,好吧 Silverlight 也有点像…。顺带一提,Android 的 Dalvik 为了效率,使用的是 register-based virtual machine。对 WebAssembly spec 感兴趣的朋友可以移步这里

作为 WebAssembly 的 MVP,C/C++ 及其类库的支持是首当其冲的。因为基于 LLVM 的平台,所以理论 LLVM 支持的语言都可以编译为 WebAssembly,C/C++,rust,甚至 .net 和 Java 也可以编译到 WebAssembly,只不过托管语言都需要附带一个巨大的runtime。

下面我们以 C/C++ 为例,我们写一个函数给 JavaScript 使用。

步骤:

  • 安装 WebAssembly 构建工具链 emscripten,针对 macOS,请参考这里
  • 安装后,执行 emcc --version 判断是否成功
  • 创建 C++ source:cat random.cc

include <random>

include <cmath>

extern “C” {

long normal_rand() {

static std::random_device rd{};
static std::mt19937 gen{rd()};
return std::lround(std::normal_distribution&amp;amp;lt;&amp;amp;gt;(0, 100)(gen));

}

} `

这里用 C++ 产生一个正态分布,期望为0,方差100的随机数,然后导出为一个 C 函数 normal_rand

执行 emcc --bind -std=c++14 --emrun -s WASM=1 -s EXPORTED_FUNCTIONS='["_normal_rand"]' -O3 -o random.html random.cc 顺利的话会在当前目录生成如下文件

`$ ls -l
total 496
-rw-r--r--  1 haoli  staff     810 Dec 24 21:44 random.cc
-rw-r--r--  1 haoli  staff  102728 Dec 24 22:17 random.html
-rw-r--r--  1 haoli  staff  120624 Dec 24 22:17 random.js
-rw-r--r--  1 haoli  staff   20130 Dec 24 22:17 random.wasm

random.wasm 就是我们的 WebAssembly,random.jsrandom.html 是模板代码,帮助我们加载 WebAssembly。

执行 emrun --no_browser --port 8821 random.html 启动一个 WebServer

用支持 WebAssembly 的浏览器访问http://localhost:8821/random.html,然后在 console 里面执行 Module._normal_rand() 即可看到结果

怎么做好吃?

古往今来,在浏览器里面尝试改善 JavaScript 性能和增强功能的尝试大约都失败了吧,前有 ActiveX,Java Applet,Flash,后有 Silverlight,Flex,NaCl。WebAssembly 应该是各个浏览器大佬的最新尝试。不过这次大家都学乖了,没人指(xi)望一个私有标准会成功,于是联合起来开发一个开放的标准。

至少目前看来,结果还是很让人欣喜的。因为开放标准的缘故,除了上面的 emscripten,还有大量的工具开始支持 WebAssembly,甚至 clang 可以直接指定 target 为 WebAssembly。加上浏览器把诸如 DOM API 以及 WebGL API 都暴露给了 WebAssembly,应用场景相当可观。首当其冲的就是游戏厂商,EpicUnity 都是 WebAssembly 的早期尝试者,他们已经把自己的游戏引擎移植到 Web 平台而不用重写代码。不仅如此, WebAssembly 还支持 non-web 的场景,比如 NodeJs 也开始支持 WebAssembly。WebAssembly 官网有个 use case 清单,列举了可能的应用场景。图形图像的处理,计算机辅助设计,AR/VR,VPN,加解密等等。到那时,前端可以玩出的花样,想象空间实在太大。

有点过于美好了哈,我们还是就此打住,拭目以待吧。

Reference

这里列举一些 WebAssembly 相关的资源,各位随喜:

  1. Funky Karts, 移植到 WebAssembly 的网页游戏,作者在网站记录了学习 WebAssembly 到移植成功的全过程。
  2. WasmExplorer,在线的 C/C++ to WebAssembly 编辑器,同时也可以查看 Assembly 内容。
  3. WasmFiddle,另一个在线编辑 WebAssembly 的工具
  4. WasmRocks WebAssembly 新闻站
  5. emscripten 官网

更多精彩洞见,请关注微信公众号:思特沃克

Share

打造企业级移动测试云平台

背景

移动技术发展到现阶段,原生、混合式技术发展的足够成熟,可以无缝融合。而随着移动技术的发展和革新,移动领域的测试技术和实践也有了一定发展:工具不再像早期一样几家独大,选择性越来越多;从浅尝辄止的实验阶段到真实项目中的自动化测试落地。这些实践在一定程度上提升了测试反馈效率,在迭代交付的过程中出色的完成了质量保证的工作,但在相对漫长的实践过程中,我们依然可以总结一些痛点:

1、移动自动化测试的执行效率远不及Web应用

有过Web自动化测试经验的同学对于Selenium肯定不会陌生,Web端的并发测试使得测试在有限资源的情况下按照我们的期望并发执行。而且由于keychain等问题,很难在测试用例之间做到互不影响、对于测试环境的清理和准备也有很大难度。

2、很难全面覆盖繁杂的测试设备

Web自动化测试关注的测试环境相对单纯,针对不同项目、产品和市场,无非是对不同的浏览器和操作系统有不同程度的支持。而对于不同浏览器也有不同的driver来支持。而在移动测试中,很难做到对众多厂商和不同操作系统设备进行模拟。

3、移动自动化框架很难支持到回归测试颗粒度

在移动端(以iOS为例),受限于Apple的机制,大部分框架很难覆盖到与iOS系统/第三方App交互的场景,例如系统通知跳转、实时通讯应用信息发送等场景。而若无法覆盖核心功能,那么自动化测试的落地实则是在给自己和团队挖坑,得不偿失。

这些问题在随着WebDriverAgent的成熟以及XCode 9的新特性 —— Multiple concurrent simulators的出现,得到了极大程度的解决,我们可以像对Web应用一样,对移动端应用在不同的simulator上并发执行测试用例,极大提升了测试反馈效率。而且,测试人员不再受限从而可以编写覆盖率更高的测试用例。

除了普适性问题之外企业对移动测试方案潜在需求?

在项目的具体实施过程中,除了我们经常被这些普遍存在的细节问题困扰之外,企业或组织级客户已经对移动端自动化测试提出了更高的要求。在一次机会给客户讲解移动端自动化测试趋势时发现,新的框架的确会使客户眼前一亮,但是,在实践上的优势无非是你比其他人先研究了什么,这样的领先微乎其微,在交流过程中观察到客户更大的痛点是:

如何同时覆盖到更多物理设备?如何更好的构建和重用基础设施?如何跨地域高效使用测试资源?

带着这几个问题,我们对比了一些现有的可用方案,例如AWS device farm。Device farm是针对移动App提供的测试服务,用户可以对在AWS托管的基于iOS和Android物理设备测试原生和混合应用。用户既可以使用各种测试框架来做自动化测试,也可以远程访问设备进行应用程序的测试和调试。

但是该解决方案也是有一定局限性的,当测试运行期间同时执行测试的设备最大只有五个,而运行测试的时间也被限制到60分钟。当然上述的限制可以根据需要适当的放松,但是企业和用户不得不承担价值不菲成本。

与AWS device farm类似,SauceLabs和Xamarin也提供类似的平台,那SauceLabs的服务举例,如果想使用无限运行时间,支持24个并发运行设备,模拟器用户需要每月承担3576刀,而如果想使用真实设备进行测试,大概需要每月花费7200刀。这种昂贵的成本对于企业很难承受,而且重要的是这种资源是无法复制,企业不得不持续为云服务支付高昂的费用。

安全性也是企业需要考虑的问题,用户不得不在云测试平台上传自己的IPA或APK。我们当然可以信赖AWS的安全机制。一些对安全性要求较高的企业来说,更想规避这样的风险。

打造私有移动真机测试平台

通过分析,对于客户的需求大概涵盖几点:真实设备、并发、成本、安全、可重用。鉴于这些需求,我们把目标进行拆分:

1.设备管理——服务发现与注册

在该实例中我们使用WebDriverAgent作为测试框架,需要运行在每一个物理设备上,我们可以把这些物理设备当作Agent集群。这些集群设备就是我们运行WebDriverAgent的服务终端,我们可以通过很简单的程序让WebDriverAgent自动在设备上运行。通过服务发现与注册机制,把WebDriverAgent服务注册在通过Ansible管理的Proxy上。而服务发现与注册不单单解决了复杂的设备管理,而且可以解决分布式团队合作时设备跨地域有效利用的问题。

2.平台数据可视化

对于一个测试平台来说,如何把所有可用的服务(机器)、服务状态、自动重启和crash报告等数据可视化给企业终端用户,是极为重要的。那老牌Apache zookeeper来说,提供了友好的服务可视化管理功能并且可以根据用户需求进行二次开发。重要的是,这些底层基础设施服务可以在之后的任何一个移动测试项目中被重用。

3.自动化测试运行和报告生成

自动化测试平台虽然提供了强大的服务(设备)管理、服务可视化等功能。而自动化测试的核心需求依然是如何保障测试的独立性、稳定性、易维护性、重用性和覆盖率。通过WebDriverAgent跨语言测试框架,我们可以像架构Web自动化测试一样来开发针对移动端的测试工程。但需要注意的是移动测试不同的是真实物理设备,而不是计算机的某个进程。另外,如何接触测试场景的相互依赖、保证测试间的独立性,以及如何清理测试环境,需要大家在进行移动端架构的时候事先考虑。

这样一来,我们如果可以解决这三个问题,就可以不受昂贵的成本限制,为企业量身定做适合自己的业务规模的移动测试私有云了,不但为企业和组织机构构建了大型测试服务平台,同时也解决了之前提到的普适性问题。

总结

随着DevOps的发展,软件工程的开发、部署、上线、应急预案等都被自动化监控和处理。如果我们依然停留在“成熟”的解决方案而缺少思考,那么留给QA/测试人员的发展空间越来越少。

我们需要通过对测试技术细节的不断归纳、对比和练习,抓住领域发展趋势和真正的客户诉求,结合其他非测试技术,帮助自己在测试技能上有所突破,同时帮助自己提升构思和落地解决方案的能力。


更多精彩洞见,请关注微信公众号:思特沃克

Share

性能测试问题与思考

性能测试对于大部分测试人员都是一个神秘地带,因为在很多公司,性能测试都是由一个性能测试团队来做,所以普通测试人员没有机会接触到真实的性能测试,因而很难学习到很多新的测试实践知识。

市面上现在有非常多关于性能测试的书籍,其中不少书籍都能够系统地介绍性能测试。今天我想通过另一种方式来介绍性能测试,那就是通过提出一些关于性能测试的问题,然后针对问题进行思考。希望通过不一样的方式让读者以另外一种视角来思考和学习性能测试实践。

1、如何在敏捷开发中做性能测试?

敏捷开发,由于其持续集成、快速反馈等特点,需要性能测试工具支持轻量化、集成CI服务器、全代码化等特性,所以传统的性能测试工具比如JMeter和LoadRunner等已经很难适用于敏捷开发。在敏捷开发中,性能测试应该需要具有以下特点:

  • 性能测试是持续集成和持续发布的一部分,尽可能早的发现性能问题,从而降低修复成本。这样可以使得很多性能问题在开发过程中被持续的尽快发现。建议将性能测试写成故事放到每个迭代里面去,见下图:
  • 性能测试脚本易读,易维护。比如代码化的脚本GatlingLocust等。下面是Gatling的DSL示例代码:
  • 可视化,报表易读,每个人都能及时了解状况 。下图为Jenkins集成了一个Gatling插件后所展现的Gatling持续测试报表。

通过在敏捷开发中做持续的性能测试,使得性能测试也可以:小步快跑->快速反馈->持续改进->持续交付。

2、如何通过数据分析更有效的做性能测试?

大部分情况下大型项目的性能测试需要的时间和人力都非常高,因为:

  • 用户并发要求高,测试硬件成本高
  • 测试功能点多,测试量大
  • 耐久性测试,测试时间长

其次是测试的有效性差,因为测试人员在测试环境中很难模拟真实用户的操作,比如访问的顺序分布,访问的强度分布以及用户端的各种访问参数。

为了解决这两个问题,应该通过用户数据分析来获得真实的用户行为数据,并用其来构造性能测试用例,其中可以用下面这个漏斗模型来进行思考:

通过这个漏斗模型可以知道,为了快速得到真实有效的性能测试数据模型,需要通过筛选并整合真实的用户数据,而并不是靠测试人员在实验室中想象出来的数据。

那如何进行用户行为分析呢?下面尝试用三个例子来进行说明。

第一个例子是一个用户注册流程,通过用户行为的数据分析可以得到每个功能点上用户的访问量。见下图:

图中展示了用户注册流程的6个步骤,分别是:

  1. Getting Started
  2. Account Information
  3. Personal Information
  4. Financial Information
  5. Review Information
  6. Thank You Page

其中第一个步骤有18606次访问,然后有71%的访问用户选择继续,但是只有13131(70.6%)到达了第二个步骤。最终只有925(5%)的用户完成了注册。由此可以知道不同步骤的真实访问比例,从而得到性能测试的数据模型和策略。

第二个例子是用户使用的浏览器的数据统计,如果性能测试需要模拟不同的浏览器,那么这些数据分析结果也可以用以确定浏览器在性能测试中的权重。

通过这个统计表可以知道使用IE浏览器的用户最多,所以在生成性能测试数据的时候应该多生成一些基于IE浏览器的数据,比如User-Agent等。

第三个例子是统计的用户访问地区,对于有些大型的互联网应用是需要进行多地区模拟来测试不同地区互相访问时的性能。这个数据统计结果可以帮助其设计更有效的这类性能测试用例。

通过这个统计表可以知道英国的用户访问量最多,而美国的访问量第二。如果应用服务器部署在美国,那么就应该尽可能的在英国架设测试服务器,通过在英国的测试服务器来测试美国的应用服务器,从而测试跨国网络的性能,并且还需要在产品环境检测英国到美国的网络性能,从而及时发现性能问题。

通过对这些真实用户数据的分析,可以设计更有效的性能测试模板,下面是一个性能测试模板的样例,其中每个功能点上圆圈中的数字代表这个功能点在真实环境中的用户访问权重。权重越大的功能点在整个性能测试模板中应该测试量更大,所花费的成本更高。

常见的Web系统用户数据跟踪与分析工具有:

  • Adobe SiteCatalyst:功能强大,使用繁琐,收费(贵)
  • Google Analytics: 功能较强,使用方便,免费和收费
  • 百度统计/腾讯分析:功能一般,免费

使用数据分析来生成数据模型的流程如下:收集数据->分析数据->生成测试数据模板->使用数据模板。

3、对大规模集群系统做性能测试应该注意什么?

通常中小型公司的IT系统都不使用大规模的集群,而只有大型公司才会大规模使用集群,导致很多测试人员没有机会实践和了解基于集群的性能测试。如果想学习基于集群的性能测试,除了常规的测试集群系统性能以外,还可以从以下几个方面进行思考,从而学习基于集群系统的性能测试:

  • 测试环境的真实性,由于大规模集群包含很多节点和服务,所以搭建和产品环境一样的测试环境的成本很高,导致测试环境的规模一般都比产品环境小很多。那测试这样的测试环境还有什么意义?
  • 一般集群都会使用负载均衡,但是由于存在多种负载均衡算法和配置,那么怎么才能保证负载均衡功能是按照设计和配置的进行工作?
  • 除了对集群系统进行整体的性能测试外,还需要单独对不同的服务和节点进行性能测试吗?

所以针对集群系统做性能测试不仅仅能测试系统的性能,还可以解决以上三个问题:性能规划,配置测试,隔离测试。

性能规划

对于一个大型的服务器应用系统,一般情况下都是由规模化的集群组建而成的。所以测试这类基于集群的服务器系统的时候,也需要将测试环境配置成和产品环境类似的集群系统。不过因为成本的原因,测试环境中的集群规模大都要小很多。可以通过测试小规模的集群,然后使用其测试结果,并通过数学建模推算产品环境的性能或者对产品环境进行性能规划。由于每个集群系统拥有各自不同的架构,配置和服务,所以其数学模型也是不同的。

配置测试

通过更改集群系统的各种配置,并在不同的配置下对集群系统进行性能测试,从而获得最优配置。比如辅助开发人员完成集群功能的开发与验证,比如负载均衡算法,热备等;以及辅助运维团队配置和调试产品环境的集群配置等。

隔离测试

对于集群系统的服务或者节点,开发这些服务的团队应该在隔离(stub)第三方依赖的环境下,各自对自己团队开发的服务进行独立的性能测试。从而尽早发现性能问题,尽早修复,避免在集群环境下发现同样的问题,增加调试和时间成本。

大规模集群系统基本都是复杂架构,环境也都是较为复杂的组织结构,而只有深入理解整个业务流程,系统架构以及环境结构才能有效地设计测试方案。

4、性能测试中的测试数据有几种类型?

测试数据一直是软件测试中的一个头疼的问题,特别是在性能测试中测试数据尤为重要,因为越真实的数据越能获得更好的结果。对于测试数据的类型可以分为以下四种:单一型,随机型,模板型,真实型。

单一型

它是通过录制或者观察,使用一个或者一类单一的测试数据来进行性能测试。这种数据的构建简单,但是数据过于单一,无法模拟真实用户。由于其数据构建简单,所以可以用于敏捷开发中的早期性能测试。

随机型

它是通过一些简单的数据规则,并结合随机算法生成的测试数据。这种数据和单一型比较,虽然增加了随机性,但是仍然缺乏真实性,并且其构建成本和性能问题的分析成本也相对较高。它可以用于上线前的大规模的多样化的综合性能测试。

模板型

它主要是通过数据分析并生成模板来构建测试数据。虽然它较随机型在一定程度上增加了用户真实性,但是准备数据的成本很高。在项目成本和资源允许范围内,可以结合模板型和随机型的方法,从而更为有效的进行性能测试。

真实型

它是通过直接导出或者重定向产品数据来做性能测试数据。它完全是真实的用户数据,构建成本较低,但是存在数据安全性的问题,比如数据泄露。在数据安全性可以得到有效保护的情况下是可以使用真实型数据来进行性能测试。

测试数据生成和管理对于一个大型项目的性能测试是十分重要的,所以如何高效的生成有效的测试数据成为了首要的任务。通过这四种测试数据类型,可以快速的判断在项目当前阶段适合使用那种类型的数据,从而避免一些弯路。

5. 其他问题

除了以上问题和思考,我还准备了一些其他的基本问题给读者自己去思考,从而通过思考问题来学习性能测试。

  • 性能测试主要包含哪些类型以及分别的作用是什么?
  • 前台页面的性能测试应该注意哪些问题?
  • 对于并发用户很少但是稳定性要求很高的系统需不需要做性能测试?为什么?
  • 对于后台有大量异步批处理需求的系统应该怎样进行性能测试?
  • Profiling是不是性能测试?什么时候应该做它?
  • 常见的性能测试工具有哪些?怎么选择性能测试工具?
  • 如果测试环境和产品环境的硬件配置不同,如何通过测试环境的测试结果评估产品环境的性能?
  • 在设计性能测试用例时需不需要考虑Think Time?
  • 中小型项目的性能测试都需要注意些什么?
  • 性能需求的来源有哪些?
  • 如何使用云服务进行超大规模性能测试?

更多精彩洞见,请关注微信公众号:思特沃克

Share

聊一聊契约测试

什么是契约

如果从契约产生的阶段来说,现有资料表明最早要追溯到西周时期的《周恭王三年裘卫典田契》,将契约文字刻写在器皿上,就是为了使契文中规定的内容得到多方承认、信守,“万年永宝用”。所以订立契约的本身,就是为了要信守,就是对诚信关系的一种确立。诚信,是我国所固有的一种优良传统,也是延续了几千年的一种民族美德,在中国儒家的思想体系里,是伦理道德内容中的一部分。

《现藏于台北故宫博物院》

现实真的是那么美好吗?小时候的价值观教育未能改变社会的现状,缺少契约精神的案例却比比皆是。

那么,契约真的要消失了吗?不尽然,在软件测试领域,我们又重新拾起了契约这把利器。

发展历程

接下来让我们把时间回溯到2011年初,回到老马的文章《集成契约测试》中来,回顾一下契约测试的起源和发展历程:

假设我们有这样一个场景:A团队负责开发API服务,B团队进行API调用消费服务。

为了保证API的正确性,我们会对外部系统的API进行测试(除非你100%相信外部系统永远正确和保持不变),这很可能就会导致一个问题,当外部系统并不那么稳定或者请求时间过长时,就会导致我们的测试效率很低,并且稳定性下降。比如当外部API挂掉导致测试失败时,你并不能完全确信是API功能被更而改导致的失败还是运行环境不稳定导致的请求失败。

最初,解决这个问题的方案是构建测试替身(Test Double),通过模拟外部API的响应行为来增强测试的稳定性和反应速度。实现手段是在测试环境中搭建一个模拟服务环境,通过设定一些请求参数来返回不同的响应内容,然后再被内部系统调用,来保证调用端的正确性。构建模拟环境时我们可以使用几种不同的测试手段,如Dummy,Fake,Stubs,Spies,Mocks等。可是,问题又来了,如果使用测试替身那如何能保证外部系统API变化时得到及时的响应,换句话说,当内部系统测试都通过的通过时,如何能保证真正的外部API没有变化?

一个比较简单的方式是部分测试使用测试替身,另外一部分测试定期调用真实的外部API,这样既保证了测试的运行效率、调用端的准确性,又能确保当真实外部系统API改变时能得到反馈。

是不是到这里就皆大欢喜了呢?

如果剧情到这里就结束的话,未免太过俗套。这个方案最大的缺陷在于API的反应速度,真实外部API的反馈周期过长,如果减少真实API测试间隔时间就又会回到文章最开始的两难境地。

那么如何解决这个问题呢?先来让我们剖析一下前面几种解决方案的共通点。

在上面的场景中,我们都是已知外部API功能来编写相应的功能测试,并且使用直接调用外部API的方式来达到验证测试的目的,这样就不可避免的带来两个问题:

第一,服务消费方对服务提供方API的更改是通过对API的测试来感知的。

第二,直接依赖于真实API的测试效果受限于API的稳定性和反映速度。

解决方式首先是依赖关系的解耦,去掉直接对外部API的依赖,而是内部和外部系统都依赖于一个双方共同认可的约定—“契约”,并且约定内容的变化会被及时感知;其次,将系统之间的集成测试,转换为由契约生成的单元测试,例如通过契约描述的内容,构建测试替身。这样,依赖契约的测试效率优于集成测试,同时契约替代外部API成为信息变更的载体。

对于契约来讲,行业内比较成熟的解决方案是基于YAML标记语言的Swagger Specification(OpenAPI Specification),或者是基于JSON格式的Pact Specification

通常的做法是API的提供者使用“契约”的形式,将功能发布在公共平台,给调用方进行说明和参考,这里我们可以暂时称之为Provider-Driven-Contract。这种做法的潜在问题是,功能提供方的API返回内容是否都满足所有API调用者的需求不得而知。所以,针对这个问题,依赖关系再一次反转,契约测试就摇身一变成为了Consumer-Driven-Contract test(CDCT), 通过给API提供方提供契约的形式,来完成功能的实现。

难道CDCT成为了问题终结者吗?请听后面分解。

注: 契约测试其中一个的典型应用场景是内外部系统之间的测试,另一个典型的例子是前后端分离后的API测试,这里不做过多展开。

契约测试的维度

1.测试覆盖范围对比(纵向)

单元测试:对软件中的基本组成单位的测试,大多数是方法函数的测试,运行速度快。

契约测试:对服务之间的功能进行的测试,运行速度基本与单元测试相同。

E2E 测试:对系统前后端或者不同系统之间的集成测试,大多通过模拟UI操作的方式实现,运行速度三者之中最慢。

2.测试效率对比(横向)

环境依赖:

  • 单元测试:程序集
  • 契约测试:程序集、依赖契约文件、虚拟路由服务
  • 端到端测试:程序集、真实路由服务、前端UI
  • 运行速度: 单元测试 > 契约测试 > 端到端测试

Pact官方给出的几个场景:

适用场景:

  • 团队能把控开发过程中的Consumer和Provider端
  • 适合Consumer驱动开发的场景
  • 对于每个独立的Consumer端,Provider端都能管理好需求。

不适用的场景:

  • 公共API或者是OAuth授权服务
  • Provider端和Consumer端没有良好的沟通渠道
  • 针对性能的测试
  • Provider端的功能性测试(Pact只测试内容和请求格式)
  • 对于不同输入有相同的输出,并未达到验证的目的
  • 当前测试输入需要依赖之前测试返回的结果

以上对比说明契约测试所要解决的问题是替代系统之间的集成测试,通过契约和单元测试的方式加速系统运行。同时也说明契约测试存在一些不适用的场景,要依据使用场景区别对待。契约测试没有取代单元测试以及E2E测试。

契约测试与CD的整合

最开始,我们的pipeline是这样的,单元测试是独立的测试,当通过单元测试后运行集成测试。此时集成测试成为了系统瓶颈,而且一旦集成测试失败,就必须被迅速修复,其他pipeline只能等待其修复,否则任何新的变更都会测试失败。

一个解决办法是将集成测试分散在每个pipeline上,每次集成测试运行的版本是当前的最新代码和其他系统的上一次通过版本之间的测试。这样解决了测试的独立性以及不会阻碍其他pipeline测试的效果,然后将通过测试的不同系统的package按照版本保存。但是这样一来,集成测试的缺点就更为明显提现出来,第一是系统部署时间长,每次集成测试需要运行同样的测试在不同pipeline上,增加了测试成本和反馈周期。

​​ ​​ 接下来,我们使用契约测试替代集成测试。这样有几点好处不仅解决了独立测试的目的,同时解决了集成测试慢和部署时间长等问题。

为了保证契约测试的正确性,契约文件由Consumer端生成,然后Provider端来实现API,我们使用CDCT来改造我们的pipeline。

我们先假设B系统希望A系统提供新功能,如果按照图中黄色步骤来提交的话,则会测试失败,原因在于此时,契约文件是最新的B-A.consumer.1.1.pact与之对应A-B.provider.1.0.jar不是最新的,所以测试失败。

按照图中步骤2运行,当提交A的pipeline时,当前版本的A已经升级到1.1,而契约文件还是1.0版本,没有break测试的情况下,最终将A-B.provider.1.1.jar提交到服务器上。​

然后按照图中步骤3运行,A-B.provider.1.1.jar和B-A.consumer.1.1.pact完美契合,最终又将B-A.consumer.1.1.pact提交到服务器。所以,改成CDCT之后,虽然产生了一定的提交顺序依赖,但是带来的更多的好处是确保契约文件的产生是调用端提出,并且保证当前最新,确保系统的正确性。

喜欢思考的同学不难发现,CDCT存在自身的缺陷,一个简单的例子是当B存在一个已有的契约约束A的一个功能,当B需要A更新其API时,是先提交B的契约测试,还是更改A的功能到最新版本?其实二者都不可行。

解决办法万变不离其宗,就是大家熟悉的不能再熟悉的重构心法,由王建总结的十六字箴言:​

我们分五步来完成API的更新:

  1. Provider端提交一个新的API来保证新功能,同时旧的API功能不变,提交并通过测试。
  2. 将Consumer端API的调用指向Provider端的新API,并更新契约文件以约束新功能。
  3. 将Provider端旧API同步更新为新API,提交并通过测试。
  4. 将Consumer端指回旧有API,其他保持不变。
  5. 将Provider端临时过渡的新API删除。

至此,我们解决了API更新时如何保证契约测试的提交顺序,如果是删除API,则直接删除Consumer端的契约测试即可。

需要思考的问题:

1.如果并行测试的话,谁先提交成功的版本,另外一个测试是否要重新运行?

设想,当两个并行pipeline A和B,同时运行时,A中跑的是A1.1和B1.0,B中跑的是B1.1和A1.0的测试,假如双方均能通过各自的测试,但是新版本不兼容(A1.1和B1.1测试失败),双方都将各自的新版本保留,这样就造成了存在相互不兼容的两个版本。目前解决方案是,人为制造一个“瓶颈”,保证同时只有一个契约测试在运行,保存的只有一个版本。

2.契约测试可维护性如何?

构建契约测试类似于单元测试,并且在Pact的框架下十分方便维护。但是,测试框架本身还有一些问题,诸如,大小写敏感,空值验证,只有一份契约文件,契约测试分组等。

(以上是基于pact 1.0的实践,pact2.0使用了正则表达式以及TypeMatching等机制解决了验证“具体”值的问题,更多详细内容请关注pact官方文档

结语

契约测试不是银弹,它不是替代E2E测试的终结者,更不是单元测试的升级换代,它更偏向于服务和服务之间的API测试,通过解耦服务依赖关系和单元测试来加快测试的运行效率。


更多精彩洞见,请关注微信公众号思特沃克

Share

前端不止:Web内容的无障碍性

网民统计报告

根据2017年7月份发布的第40次中国互联网络发展状况统计报告:

截至 2017 年 6 月,中国网民规模达 7.51 亿,中国手机网民规模达 7.24 亿, 中国网民中农村网民占比 26.7%,规模为 2.01 亿。

截至 2017 年 6 月,中国网民通过台式电脑和笔记本电脑接入互联网的比例分别为 55.0% 和 36.5%;手机上网使用率为 96.3%,平板电脑上网使用率为 28.7%;电视上网使用率为 26.7%。

截至 2017 年 6 月,我国非网民规模为 6.32 亿。上网技能缺失以及文化水平限制仍是阻碍非网民上网的重要原因。调查显示,因不懂电脑 / 网络,不懂拼音等知识水平限制而不上网的比例分别为 52.6% 和 26.9%;由于不需要 / 不感兴趣而不上网的比例为 11.2%;受没有电脑,当地无法连接互联网等上网设施限制而无法上网的比例分别为 9.3% 和 6.2%。

提升非网民上网技能,降低上网成本以及提升非网民对互联网需求是带动非网民上网的主要因素。

信息无障碍

今天,我想讨论一个关键的词语——信息无障碍。

信息无障碍,英文词语来自“Accessibility”,是指任何人(注意是任何人,无论是健全人还是残疾人,无论是年轻人还是老年人等等)在任何情况下都能平等地、方便地、无障碍地获取信息、利用信息。

首先,我们要对Accessibility(无障碍)的一些错误认识进行一些纠正:这样一个词,很多人自然地跟残障人士联系起来,因为经常可以看到无障碍坡道、无障碍洗手间这样的词语。

信息无障碍更多强调的是对所有人都平等,方便的获取信息。比如:键盘上的F和J的凸起基准键。它的作用就是方便任何人可以精准的找到键盘字母的位置,从而可以在不看键盘的情况下,快速的打字,俗称“盲打”,大家都知道它的含义,没有人会把这个词理解为“盲人打字”吧。

(键盘基准键)

我国非网民规模为 6.32 亿,由于个人主观意愿,比如:不需要 / 不感兴趣而不上网的比例仅仅占11.2%。

那么,剩下的88.8%,也就是大约5.612亿人,是有上网需求的,但因为其他各种原因而不能上网。信息的公平使用和访问,是所有人的基本权利,如果你不能像身边其它人一样公平的获取信息,那意味着你不能公平接受教育、就业、独立生活等等。

我为什么会注意

我是谁?我为什么关心这些?这不是个哲学问题。每个人身上都有很多的标签,但在这里,我的标签是一个普通的Web开发工程师,一个新科技产物的使用者,一个信息的生产者和使用者,一个能“无障碍”获取信息的个体。而我的生活和我的工作让我开始关注“无障碍”这样一个词。

在互联网的大环境下,所有人的生活都方便了不少,我们可以远程办公,远程接受教育,网上购物,现在甚至连买菜、买水果都不需要出门,就这段时间,我妈有时都会直接在网上买菜和水果。

在我们享受这种生活便利的同时,我们也常常听到一些声音,比如:“这些都是年轻人的东西”,“我家小孩手机app玩的可溜了”。

前几天,我去办理港澳通行证,其实已经比我五年前办护照时方便很多,然而,在市政府政务中心的自动办理出境机器旁边,我会听到有几位年长的人说“这个机器不会用,怎么办?”,另一个人说,“没事,有个警察在旁边帮你操作”。这就意味着,如果旁边没有那个警察帮着操作,那么就不方便使用了,至少不是对所有人都方便。

科技的便利性看来还不是对所有人都便利,其实它还是需要一定的学习成本。

因为在外企的缘故,我有幸参加过一些海外的项目,在需求的实现过程中,客户对应用的无障碍性都会有一定的要求,于是我从中学习到了不少的相关知识。当然我也去过一些其他国家,跟不同国家的同事讨论过这方面的问题,也听他们介绍了一些做的不错的项目。所以我来举个例子:

比如,澳大利亚政府的“网络可访问性国内过渡战略”(NTS)规定,所有政府网站及其内容必须在 2012 年 12 月 31 日以前达到 WCAG 2.0 的 A 级合规要求,并在 2014 年 12 月 31 日之前达到 AA 级合规要求。

WCAG是万维网联盟(W3C)发布的一套名为“Web Content Accessibility Guidelines (WCAG) ”的网络内容可访问性指引。该指引目前是网络可访问性的国际标准。合规等级分为三级(A、AA 和 AAA)。

相关达到 WCAG 2.0 的 A 级合规要求的网站,例如:澳大利亚官方政府网站澳大利亚政府留学网站等,体验一下他们在Web内容无障碍性的一些实践,比如:只通过tab和enter来导航到不同的网站区域。

如果反观一下国内的一些相关网站,无障碍访问的体验感一下就降低了不少。

是能力问题还是被忽略了?

有时候,我在想这样一个问题,到底是我们的能力问题,还是问题被忽略了,当然大部分人的答案会是后者。

但我有时候会认为是前者,因为我们忽视了这个问题,所以导致其实我们也缺乏这方面的能力,无论是开发还是设计。

“目前我国99%的网站,由于没有实现无障碍,盲人难以正常浏览访问网站。”省盲协主席、中山大学工学院教授富明慧于2013年说的。富明慧本身就是一名盲人,他失明后发明了电脑盲文输入法。他说,加快网站无障碍改造,政府部门网站应该先行一步。

如果你在一个互联网公司工作,你大可在周边一问,比如:你听说过Web Accessibility?或者你知道怎么做才是最佳的方式吗?我们的产品里面有做这个?会作为代码和质量审核的一部分吗?

从哪里开始

连续追问这几个问题,的确会让不少人哑口无言,包括我自己在内。首先,我要承认这个不是一件容易的事情,否则不会有今天这样的一个讨论和思考。

我认为无障碍性的实现,应该是一个端到端的过程,不是设计师(UX/XD),不是开发(Dev),不是质量分析师(QA)某个人的责任,而是整个产品研发过程中所有人的责任,也许从产品构思的那天的就要开始考虑(比如:用户群体)。

其实这是个如何去做的话题会比较大,但是我想在这里举几个简单例子,让大家产生一些共鸣,也许从明天开始,在写代码和设计的过程中,你就会注意这些小的细节。

例子:通过Tab切换聚焦的的顺序

由于行动障碍而无法使用鼠标的人,会使用键盘进行页面的浏览。而页面上DOM的顺序会决定Tab切换时聚焦(focus)的顺序,我们知道CSS可以改变DOM在页面上的显示位置逻辑,但是DOM本身的顺序没有改变,这就容易导致Tab切换时给键盘使用者带来困惑。比如:

<div style="width:200px">
  <button style="float: right">右边菜单按钮</button>
  <button>左边</button>
  <button>中间</button>
</div>

当使用tab进行切换时,并不是最先聚焦到“左边”这个按钮,而是右边菜单,这就和页面上看到的逻辑产生了冲突。

例子:通过tabindex聚焦弹出框

你有没有注意到Bootstrap的模态框是这么写的:

<button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">
  弹出
</button>
<div class="modal fade" id="myModal" tabindex="-1" ...其他属性省略>...</div>

当tabindex=-1时,表示当前元素必须要被聚焦,如果是元素弹出框,就需要使用tabindex,这样才能保证使用键盘的用户可以理解通过tab切换到模态框中的各个元素。如果你没有使用像Bootstrap这样的框架,那么当用其他前端框架实现自定义的模态框时,请务必考虑这个细节。

例子:请自定义元素的outline样式

你知道CSS中{ outline:none }对于网站的无障碍访问性是一个致命的做法吗?为什么我们还会这么做呢?因为元素在被聚焦时,会有一个蓝色的轮廓,而出于视觉效果的考虑,被认为是“不好看的”,所以被去掉了。

于是,当通过tab进行页面浏览时,很难立刻知道光标当前聚焦在哪一个元素(链接或者输入框)。这种情况,我们需要为它的聚焦效果提供一个另外的设计,以便同时保证它的功能性和视觉效果。更多关于{ outline:none }的内容,大家可以参考:http://www.outlinenone.com/

例子:设计页面时,请满足文字上的前后景颜色的对比度

(文字和背景的颜色对比)

WCAG 2.0 的1.4.3条对页面上文字的对比度有一个最低的要求4.5 : 1,目的很明显是为了保证色盲/色弱人群可以无障碍的访问网站的内容。假如说你是产品经理,有一天设计师告诉你,这个设计可能导致10个用户里面有1个用户存在访问障碍,阅读困难,你能接受吗?我想谁都接受不了。

有什么工具可以帮助检测网站的无障碍性吗?

对于普通使用者,可以使用Chrome的审计功能和Accessibility Developer Tools(Chrome插件),它能帮助你自动的检测网页的的可访问性问题,以及帮助你提供相关的修正信息。

(Chrome的审计功能)

Accessibility Developer Tools(Chrome插件)

对于开发人员,可以做一些自动化的检测,比如:使用pa11y这样一个工具,它是基于HTML codeSinffer以及PhantomJS制作而成的网站内容A11y(Accessibility,无障碍性)自动化检查工具。pa11y出现在ThoughtWorks 2017年3月的技术雷达中,我的同事写过一篇详细的文章来介绍这个工具:《无障碍性测试工具 Pa11y》

当然,最重要和最有效的检验方式是用户测试,比如:假设你自己是一个纯键盘的网站浏览者,尝试一下用键盘浏览自己开发的网站,是否能够方便的导航到网页的各个部分,并进行无障碍的阅读和交互。

还有其他一些重要的关注点吗?

有的,比如:基本HTML的语义化,alternative text的使用,ARIA标签属性的使用,都可以帮助屏幕阅读器有效的告诉使用者当前的元素类型和作用。

不要小看这些细节

​不要小看这些细节或者说基础功能,它涵盖的人群非常大,根据国家统计局最新数据,在中国,单是65岁的老年人已经超过1.5亿人口。加上其它障碍人群,以及第二语言学习者,等环境障碍人群,粗粗一算,这么简单的特性就能方便好几亿的用户,为什么要省略呢?

我在写这篇文章的时候,也去查了不少的资料,我觉得知乎上有一个人说很好:对无障碍性一个最大的误区是,将信息无障碍,当做产品的情怀功能,而非基础功能或Bug去对待。


更多精彩洞见,请关注微信公众号:思特沃克

Share