phantomJs之殇,chrome-headless之生

技术雷达快讯:自2017年中以来,Chrome用户可以选择以headless模式运行浏览器。此功能非常适合运行前端浏览器测试,而无需在屏幕上显示操作过程。在此之前,这主要是PhantomJS的领地,但Headless Chrome正在迅速取代这个由JavaScript驱动的WebKit方法。Headless Chrome浏览器的测试运行速度要快得多,而且行为上更像一个真正的浏览器,虽然我们的团队发现它比PhantomJS使用更多的内存。有了这些优势,用于前端测试的Headless Chrome很可能成为事实上的标准。

随着Google在Chrome 59版本放出了headless模式,Ariya Hidayat决定放弃对Phantom.js的维护,这也标示着Phantom.js 统治fully functional headless browser的时代将被chrome-headless代替。

Headless Browser

也许很多人对无头浏览器还是很陌生,我们先来看看维基百科的解释:

A headless browser is a web browser without a graphical user interface.

Headless browsers provide automated control of a web page in an environment similar to popular web browsers, but are executed via a command-line interface or using network communication.

对,就是没有页面的浏览器。多用于测试web、截图、图像对比、测试前端代码、爬虫(虽然很慢)、监控网站性能等。

为什么要使用headless测试?

headless broswer可以给测试带来显著好处:

  1. 对于UI自动化测试,少了真实浏览器加载css,js以及渲染页面的工作。无头测试要比真实浏览器快的多。
  2. 可以在无界面的服务器或CI上运行测试,减少了外界的干扰,使自动化测试更稳定。
  3. 在一台机器上可以模拟运行多个无头浏览器,方便进行并发测试。

headless browser有什么缺陷?

以phantomjs为例

  1. 虽然Phantom.js 是fully functional headless browser,但是它和真正的浏览器还是有很大的差别,并不能完全模拟真实的用户操作。很多时候,我们在Phantom.js发现一些问题,但是调试了半天发现是Phantom.js自己的问题。
  2. 将近2k的issue,仍然需要人去修复。
  3. Javascript天生单线程的弱点,需要用异步方式来模拟多线程,随之而来的callback地狱,对于新手而言非常痛苦,不过随着es6的广泛应用,我们可以用promise来解决多重嵌套回调函数的问题。
  4. 虽然webdriver支持htmlunit与phantomjs,但由于没有任何界面,当我们需要进行调试或复现问题时,就非常麻烦。

那么Headless Chrome与上面提到fully functional headless browser又有什么不同呢?

什么是Headless Chrome?

Headless Chrome 是 Chrome 浏览器的无界面形态,可以在不打开浏览器的前提下,使用所有Chrome支持的特性,在命令行中运行你的脚本。相比于其他浏览器,Headless Chrome 能够更加便捷的运行web自动化测试、编写爬虫、截取图等功能。

有的人肯定会问:看起来它的作用和phantomjs没什么具体的差别?

对,是的,Headless Chrome 发布就是来代替phantomjs。

我们凭什么换用Headless Chrome?

  1. 我爸是Google,那么就意味不会出现phantomjs近2k问题没人维护的尴尬局面。 比phantomjs有更快更好的性能。
  2. 有人已经做过实验,同一任务,Headless Chrome要比现phantomjs更加快速的完成任务,且占用内存更少。https://hackernoon.com/benchmark-headless-chrome-vs-phantomjs-e7f44c6956c
  3. chrome对ECMAScript 2017 (ES8)支持,同样headless随着chrome更新,意味着我们也可以使用最新的js语法来编写的脚本,例如async,await等。
  4. 完全真实的浏览器操作,chrome headless支持所有chrome特性。
  5. 更加便利的调试,我们只需要在命令行中加入–remote-debugging-port=9222,再打开浏览器输入localhost:9222(ip为实际运行命令的ip地址)就能进入调试界面。

能带给QA以及项目什么好处?

前端测试改进

以目前的项目来说,之前的前端单元测试以及组件测试是用karma在phantomjs运行的,非常不稳定,在远端CI上运行时经常会莫名其妙的挂掉,也找不出来具体的原因,自从Headless Chrome推出后,我们将phantomjs切换成Headless Chrome,再也没有出现过异常情况,切换也非常简单,只需要把karma.conf.js文件中的配置改下就OK了。如下

customLaunchers: { myChrome: { base: 'ChromeHeadless', flags: ['--no-sandbox', '--disable-gpu', '--remote-debugging-port=9222'] } },

browsers: ['myChrome'],

UI功能测试改进

原因一,Chrome-headless能够完全像真实浏览器一样完成用户所有操作,再也不用担心跑测试时,浏览器受到干扰,造成测试失败

原因二,之前如果我们像要在CI上运行UI自动化测试,非常麻烦。必须使用Xvfb帮助才能在无界面的Linux上 运行UI自动化测试。(Xvfb是一个实现了X11显示服务协议的显示服务器。 不同于其他显示服务器,Xvfb在内存中执行所有的图形操作,不需要借助任何显示设备。)现在也只需要在webdriver启动时,设置一下chrome option即可,以capybara为例:

Capybara.register_driver :selenium_chrome do |app|
Capybara::Selenium::Driver.new(app, browser: :chrome,
                               desired_capabilities: {
                                   "chromeOptions" => {
                                       "args" => [ "--incognito",
                                                   "--allow-running-insecure-content",
                                                   "--headless",
                                                   "--disable-gpu"
                                   ]}
                               })
end

无缝切换,只需更改下配置,就可以提高运行速度与稳定性,何乐而不为。

Google终极大招

Google 最近放出了终极大招——Puppeteer(Puppeteer is a Node library which provides a high-level API to control headless Chrome over the DevTools Protocol. It can also be configured to use full (non-headless) Chrome.)

类似于webdriver的高级别的api,去帮助我们通过DevTools协议控制无界面Chrome。

在puppteteer之前,我们要控制chrome headless需要使用chrome-remote-interface来实现,但是它比 Puppeteer API 更接近低层次实现,无论是阅读还是编写都要比puppteteer更复杂。也没有具体的dom操作,尤其是我们要模拟一下click事件,input事件等,就显得力不从心了。

我们用同样2段代码来对比一下2个库的区别。

首先来看看 chrome-remote-interface

const chromeLauncher = require('chrome-launcher');
const CDP = require('chrome-remote-interface');
const fs = require('fs');
function launchChrome(headless=true) {
return chromeLauncher.launch({
// port: 9222, // Uncomment to force a specific port of your choice.
 chromeFlags: [
'--window-size=412,732',
'--disable-gpu',
   headless ? '--headless' : ''
]
});
}
(async function() {
  const chrome = await launchChrome();
  const protocol = await CDP({port: chrome.port});
  const {Page, Runtime} = protocol;
  await Promise.all([Page.enable(), Runtime.enable()]);
  Page.navigate({url: 'https://www.github.com/'});
  await Page.loadEventFired(
      console.log("start")
  );
  const {data} = await Page.captureScreenshot();
  fs.writeFileSync('example.png', Buffer.from(data, 'base64'));
  // Wait for window.onload before doing stuff.  
   protocol.close();
   chrome.kill(); // Kill Chrome.

再来看看 puppeteer

const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.github.com');
await page.screenshot({path: 'example.png'});
await browser.close();
})();

对,就是这么简短明了,更接近自然语言。没有callback,几行代码就能搞定我们所需的一切。

总结

目前Headless Chrome仍然存在一些问题,还需要不断完善,我们应该拥抱变化,适应它,让它给我们的工作带来更多帮助。


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

Share

QA请勿忘初心

让我们回顾一下QA与QC的区别:

Quality Assurance :The planned and systematic activities implemented in a quality system so that quality requirements for a product or service will be fulfilled.

Quality Control :The observation techniques and activities used to fulfill requirements for quality.

QA的工作涉及软件研发流程的各个环节,且涉及到每一位参与研发的人员,但质量保证工作又不涉及具体的软件研发细节,侧重于整个流程。

QC则侧重于点,利用各种方法去检查某个功能是否满足业务需求。

ThoughtWorks 的QA则是这两者的混合体,既要保证开发流程的质量,又要保证Story的功能正确。

我来ThoughtWorks已经3年了,当过BQConf讲师与主持人,参加过公司内各类测试相关活动,也阅读过邮件中分享的关于测试的话题,大部分人关注点都离不开自动化测试,面试的QA也说想到ThoughtWorks来学习高深的自动化测试,仿佛自动化测试代表了整个QA界,我反对盲目的自动化测试,确切的说反对盲目的UI自动化测试。很多QA在自动化测试海洋里迷失了自己。

我要强调自动化测试:真的没有银弹。

QA的最终价值体现

Faster Delivery Of Quality Software From Idea To Consumer

确保项目的正确性

所以自动化测试只是其中的一小部分。

如上图顶部和底部的文字是对一个QA所能带给项目的总结:“我们在开发正确的产品吗?如果是,那么我们开发的产品正确吗?”所以QA首先需要在整个项目过程中不断询问所有成员上述问题,确保团队是在开发客户所需的产品,而不是自己YY出来的产品。

确保流程的正确性

Quality is not just in the software but also in the process.

质量从来都不只是QA的职责,而是整个团队的职责。但QA如果自己都不注重,不督促组内成员改进质量,再将责任强加于整个团队,那么产品质量又何谈提升与保证。

中间的图片从一个QA的角度表明了一个用户故事的生命周期以及QA如何参与其中每个环节。

首先BA和客户将要开发的用户故事列出之后,BA与QA可以一起结对编写具体用户故事的内容,场景与验收条件,利用自己对业务以及系统的熟悉度,尽量配合BA将用户故事中坑排除掉。

所有参与用户故事kick off的角色,都应该提前了解用户故事内容。在kick off过程中,提出自己对用户故事的疑问,尽量在这个阶段解决掉业务需求上的问题。

在完成kick off后,QA可以和开发人员一起结对编写unit test以及Automated Acceptance Tests,身为一个敏捷QA,我们起码要了解团队选用的单元测试工具,熟悉项目的技术架构,这样更好的便于我们把控整个项目质量,在与开发人员结对的过程中,帮助开发人员分析业务场景的分支,来确保单元测试覆盖的是正确的场景,而不是为了交代上级随便乱写的单元测试,这也可以帮助QA熟悉代码,提高编码能力。

当开发人员完成编码工作后,这时QA、UX、BA、开发人员一起检查用户故事,是否按照用户故事来检查是否完成对应的功能。UX也可发表对用户故事UI以及交互的一些看法,有任何问题及时讨论后,将问题尽早的反馈给客户。

当开发人员交付一部分功能之后,QA就可以做常规的用户故事测试,几个迭代之后,QA开始进行跨功能需求测试和探索性测试等。根据探索性测试的结果,QA可能会调整测试策略,调整测试优先级,完善测试用例等等。

上面这些QA实践貌似已经很完美,其实还差最重要的一环:uality Analysis 。每次发布后,我们总以为我们发布了一个完美的产品,但却总能在新迭代开发过程中发现之前存在的问题,历史总是惊人的相似,为什么,没有分析总结问题,以及相应的预防手段,那么同样的问题只会重现。

同时我们也要回顾下自己在工作中真的将这些敏捷实践都应用到工作中了吗?我想或多或少的都有所欠缺。对于一个QA来说,不应循规蹈矩照搬敏捷实践。例如,在kick off中,发现开发人员、UX对用户故事涉及的场景以及内容了解不清楚,QA也可能漏掉一些测试场景,那么我们可以在kick off之前,加入一个pre-kick off的实践,留出时间,让每个角色都能够完整了解用户故事。在kick off之中,UX没有办法完整的确认页面的字体大小或者颜色等是否正确,那么在sign off之后,我们也加入一个UX-test实践,帮助UX更好解决这些问题。

所以每个项目也应都有适合自己项目的敏捷实践,发现项目存在的问题,持续改进才是最佳实践。

再来谈谈自动化测试

上面的测试金字塔对于大家来说再熟悉不过了,对于自动化测试来说最有价值的仍然是单元测试,但对于QA来说无疑最复杂的。

大部分QA或者tester,仍然以UI自动化为重心。之所以反对盲目的UI自动化测试,是因为变化频繁的UI设计,投入产生比极低,这都应该让我们重新思考下UI自动化的价值。

我们需要一个实施UI自动化的正确方式:

  • 能不用UI自动化测试就不用,梳理业务主线,只保留用户操作最频繁、交互最多的场景。
  • 根据面向对象设计的原则,构建适合项目的UI自动化框架,无论自己编写框架,还是采用开源框架。
  • 尽量采用独立测试数据,确保运行测试不受影响。例如采用mock数据库或者每次运行时还原测试数据库。

回到正题,面对自动化测试的大潮,QA应该关注什么?

  • 编码规范。这是个真实例子,开发人员对于类名命名没有用Camel-Case,造成在Linux系统中部署不成功,Python中乱使用缩进等。其实这些都可以避免,例如开发工具加入自动检查,或者在CI上加入校验编码规范的步骤,采用一些工具就可以达到目的,比如jshint,RuboCop等。
  • 结对完成单元测试或API测试等,一方面可以提高QA的编码能力,另一面可以给出开发人员一些建议,将单元测试覆盖到更多的场景。

例如,如果你们项目采用React作为前端框架,如果你不能理解React virtual dom 与jsx,当我们在写UI自动化脚本时,你会发现根本无法进行下去,日常中我们需要定位的元素全是这样:

<div class="styles__formField___1fyGy">
    <input type="text" placeholder="Email">
    <svg class="styles__formIcon___37VGd" viewBox="0 0 24 24" style="display: inline-block; fill: rgba(0, 0, 0, 0.870588); height: 24px; width: 24px; user-select: none; transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;">
        <path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z">
        </path>
    </svg>
</div>

所有的页面都是js渲染出来的,如果你懂jsx,就知道只需要在对于的Component render方法中更改加入id等元素就可以搞定。

render() {
    return (
        <div>
            <input type="text" placeholder="Email" id="Email">
        </div>
    )
}

控制单元测试覆盖率,100%的单元测试覆盖率当然是最好的,但如果交付压力大,和客户商量后,我们可以尽量覆盖业务主线,而不是为了达到覆盖率延误了交付周期。

再来谈谈质量分析

作为一个QA,我们不仅要检测项目中存在的问题,也要改进团队的实践活动,更重要的是预防问题的发生。

  • 每次bug bash或相应迭代完成后,要分析统计,找出产生缺陷的环节,并采取措施防止问题再现。例如每次release或者bug bash之后,可以按照功能模块与bug类型进行统计划分,分析统计bug的成因,例如某次迭代我们bug数量激增,经调查,发现我们对某些模块的前端代码进行了重构,但缺乏相应的单元测试与集成测试,造成了我们没有及时发现bug。之后我们就对应的采取措施防止问题再现。
  • 总结分析报告,及时反馈这些信息给团队。总结分析是一个长期的任务,每次bug数量的变动,都会直接体现整个团队上次迭代的开发质量,例如bug数量减少了,可以鼓励成员再接再厉。或者某几次迭代某些模块bug呈上升趋势,那么就需要组织团队一起讨论问题根源,采取措施防止问题重现。
  • 利用代码质量分析工具,帮助我们尽早预防问题的发生。例如Sonar代码质量管理平台,可以帮助我们从代码复杂度,重复代码,单元测试覆盖率,编码规范等纬度来分析代码所存在的问题。当然也有其他的开源工具,像RubyCritic,/plato不同的语言都会有相应的工具。
  • 在线监控,利用像newrelic,airbrake等监控工具对部署在本地或在云中的Web应用程序进行监控、故障修复、诊断、线程分析以及容量计划。这样不管产品环境有任何问题,我们都能及时响应,尽早修复,减低损失。

最后让我们再看看QA应具有那些能力与技能

软技能方面包括风险控制、辅导他人、沟通能力、分析定位等。技能方面则包括缺陷管理、流程改进、测试分析、可用性测试、性能测试、安全测试等。

写在最后

回顾上述实践,其实我们可以做的更好,而不是把团队的质量全都交给自动化,回归QA的应有的初心,让我们从各个方面改进质量,带给团队更好的未来。


更多精彩文章,请关注微信公众号:软件乌托邦

Share

都100%代码覆盖了,还会有什么问题?

引言

(图片来自:http://t.cn/R06rQHi

很多人看到这个标题时,都会想“你都100%代码覆盖了,怎么还会有问题呢?” 让我们看一下代码例子:

public class TestCalculator {

       public Double add(Double a, Double b) {

              return a + b;}

}

再看看用junit写出的测试代码:

@Test
public void testAdd() {

       Double a = new Double(1);

       Double b = new Double(2);

       Double c = new Double(3);

       assertEquals(c, testCalculator.add(a, b));

}

当我们使用EclEmma或者Jacoco来进行覆盖测试时,对于这个类,我们将得到100%测试覆盖率。

一切看起来都那么的完美,真是这样的吗?

好吧,让我们来来看看另一个测试,当其中一个变量为null时,返回值将会是什么?

@Test
public void testAddNullPointerException() {

       Double a = new Double(1);

       Double b = null;

       Double c = new Double(3);

       assertEquals(c, testCalculator.add(a, b));

}

好了,你会发现尽管覆盖率为100%,但程序却抛出了NullPointerException。

那么肯定有人会问,这样的话单元测试覆盖率的高低都不能作为衡量项目代码质量的指标,那么我们要单元测试还有什么用?

首先,我想我们可能搞错了测试覆盖的定义。

我们先听听Martin Fowler对于测试覆盖的定义

Test coverage is a useful tool for finding untested parts of a codebase. Test coverage is of little use as a numeric statement of how good your tests are.

(图片来自:https://martinfowler.com/bliki/TestCoverage.html

他认为:把测试覆盖作为质量目标没有任何意义,我们应该把它作为一种发现未被测试覆盖的代码的手段。

所以100%的代码覆盖率还值得追求吗?

当然,这应该是每个程序员毕生的追求之一,但是如果从项目角度考虑ROI(投入产出比),对于需要快速上线的短期项目,需要注重的是让测试覆盖核心功能代码。如果你的项目是一个长期项目,那么高覆盖率是非常有必要的,它意味着高可维护性,以及更少的bug。(前提是你的测试采用TDD/BDD方式编写,我见过将测试代码写的一团糟的人,看着他的代码,我宁愿重新写一遍)

那么对于一个项目来说,覆盖率应该达到多少?

其实没有适用于所有项目的数值,每个项目都应有自己的阈值,但共性是,测试必须覆盖主要业务场景,代码的逻辑分支也必须尽可能的覆盖。

如何改进你的项目代码覆盖率?

首先我们要阅读和理解项目代码,找出其中需要测试并且与业务强相关的代码,结合sonar等代码质量管理平台,从代码编写规范、复杂度、重复代码等方面进行代码重构,进一步提高项目的可维护性与可读性。

这也意味着重构,重构的同时,你需要更多的测试来保证你重构代码的正确性。

其次要对code coverage进行度量分析,那么我们应该怎么度量code coverage?

一般来说我们从以下四个维度来度量,如上图所示:

  1. 行覆盖率(line coverage):度量被测代码中每个可执行语句是否都被执行到,但不包括java import,空行,注释等。
  2. 函数覆盖率(function coverage):度量被测代码中每个定义的函数是否都被调用。
  3. 分支覆盖率(branch coverage):度量被测代码中每一个判定的分支是否都被测试到。
  4. 语句覆盖率(statement coverage):度量被测代码是否每个语句都被执行。

所以行覆盖率的高低不能说明项目的好坏,我们要从多方面进行思考,一般我们遵循的标准应是:函数覆盖率 > 分支覆盖率 > 语句覆盖率**

代码覆盖率最重要的意义在于:

  • 阅读分析之前项目中未覆盖部分的代码,进而反推在前期QA以及相关测试人员在进行黑盒测试设计时是否考虑充分,没有覆盖到的代码是否是测试设计的盲点,为什么没有考虑到?是需求或者UX设计不够清晰,还是测试设计的理解有误。
  • 检测出程序中的废代码,可以逆向反推代码设计中不合理的地方,提醒设计/开发人员理清代码逻辑关系,提升代码质量。
  • 代码覆盖率高不能说明代码质量高,但是反过来看,代码覆盖率低,代码质量绝对不会高到哪里去,可以作为测试自我审视的重要工具之一。

结束语

单元测试的覆盖率并不只是为了取悦客户或者管理层的数据,它能够实实在在反应项目中代码的健康程度,帮助我们更好的改善了代码的质量,增加了我们对所编写代码的信心。


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

Share