声明:本文翻译自How Browser Work,我将会逐步发布所翻译的章节。

网页浏览器可以算得上世界使用范围最广的软件了,在这篇文章中,我将介绍下浏览器的内部工作机制。你将会理解从你在地址栏输入“google.com” 到你看到Google网站页面的这个期间究竟发生了什么。

 

1. 简介

1.1 浏览器

     如今使用范围较广的浏览器有Internet Explorer,Firefox,Safari,Chrome和Opera。我将简单介绍几个开源浏览器——Firefox,Chrome,Safari(部分开源)。根据StatCounter browser statistics,现在(2011/08)Firefox,Safari,Chrome在全世界市场占有率已经接近60%。所以开源浏览器是浏览器市场中一只不可小觑的力量。

 

1.2 浏览器的主要功能

     浏览器的主要功能是从web服务器上获取用户所请求的资源并展示在浏览器窗口中。在大多数情况下,用户所请求的资源是HTML文档,当然也可以是PDF,图片,音频等其他文件类型。资源的位置是采用URI(Uniform Resource Identifier)标识的。

     浏览器解释、显示HTML文档的方式定义在HTML(http://www.w3.org/TR/REC-html40/)、CSS(http://www.w3.org/TR/CSS/)规范中。这些规范是由web标准化组织W3C维护的。

     很多年前,很多浏览器开发商都只遵从了部分规范,并且发展出他们自己对标准的扩展,这导致了很多严重的兼容性问题。而现在,大部分浏览器都遵从了这些规范。

     浏览器的用户界面大都类似,基本都包括了以下几个控件:

  • 用于输入地址的地址栏
  • 前进、返回按钮
  • 加为书签功能选项
  • 用于刷新、终止当前文档加载过程的按钮
  • 用于返回主页面的按钮

1.3 浏览器架构概述

     浏览器的主要组件见下图(1.1):

     1.  用户界面 - 包括地址栏,前进、后退按钮、加书签按钮等。包括了除了用于展示网页窗口的其他所有用户可以看见的部分。

     2.  浏览器引擎 - 配置UI和渲染引擎之间动作

     3.  渲染引擎 - 展示请求到的内容。例如,请求的资源是一个HTML文档,渲染引擎就会解析这个HTML文档和CSS并且把解析好的内容展示在网页窗口上。

     4.  Networking - 用于网络交互,例如HTTP 请求。它有一个独立于操作系统的接口,对于每个操作系统有不同实现。

     5.  用户界面后台 - 用于绘制一些基本的控件如下拉框,单选框等。它暴露了一组平台独立的接口,在底层调用了操作系统的用户界面方法进行绘制组件。

     6.  JavaScript引擎 - 用于解释执行javascript

     7.  数据存储 - 浏览器需要存储一些数据到本地磁盘上,例如cookie。新的HTML规范(HTML5)定义一个 web database ,提供一个嵌入浏览器中的完整的轻量级数据库。

 

          图 1: 浏览器主要组件图

     值得注意的是,Chrome浏览器与其他浏览器不同,它为每个tab创建了一个独立的渲染引擎,每个tab是一个独立的进程。

 

2. 渲染引擎

     渲染引擎的职责当然就是渲染了,具体点就是把获取到的内容展示到屏幕上。

     一般情况下,渲染引擎可以展示HTML、XML和图片,当然,可以通过安装插件或者浏览器扩展展示其他类型的内容。如通过安装PDF查看器插件展示PDF文件。当然,在本章,我们还是关注最主要的功能:展示应用了CSS的HTML、图片。

 

2.1 渲染引擎

     Firefox、Chrome、Safari分别使用了两种渲染引擎。Firefox使用的是Gecko - 一个由Mozilla研发的渲染引擎。Safari和Chrome则都使用Webkit做为其渲染引擎。

     Webkit最初是Linux上的开源渲染引擎,后来Apple对Webkit进行了修改使其支持Mac和Windows平台。更多详细信息请参见webkit.org。

 

2.2 主流程

     渲染引擎会首先调用Networking获取被请求的文档的内容。一般文档会被划分为8k大小的块进行传输。

     在此之后,渲染引擎的处理流程如下:

 

 

     图 2:渲染引擎主流程

     首先,渲染引擎会解析HTML文档构建DOM树,把遇到的HTML标记转换成“内容树”上的DOM节点。同时它也会解析外部CSS文件和HTML文档中的样式数据,然后根据这些样式数据和“内容树”创建另一棵树-“渲染树”。

 

     渲染树包含一些带有颜色、大小信息的矩形,这些矩形按照其在屏幕上展示的顺序进行排列。

 

     渲染树构建完成后,将进入“布局”环节,在这个环节中会给每个节点设置其将被展示的位置坐标。接下来将进入“显示”环节 - 遍历整个渲染树,通过用户界面后台把各个节点展示在屏幕上。

 

     值得一提的是,这是个顺序的流程,为了获得更好的用户体验,渲染引擎会尽可能快地把内容显示在屏幕上。它不会等到所有的HTML文档解析完成才开始构建“渲染树”,当渲染引擎还在获取、解析其他内容时,已加载到的部分内容会先被展示在屏幕上。

 

 

 

 

Thread per connection VS Event loop

Advantage:
more memory consumption thread blocked waiting for I/O operation

  • Q: So why not Event loop, callbacks, non-blocking I/O
  • A: cultural and infrastructural.
    1. cultural, we thought that the I/O should be blocking.
    2. infrastructural
      • single threaded event loops require I/O to be non-blocking, but most libraries are not.
      • too much options: EventMachine, Twisted
  • Q: Why javascript?
  • A: javascript has the following features:
    1. Anonymous functions, closures. – easy to create callback
    2. Only one callback at a time. – only one callback at a time
    3. I/O through DOM event callbacks –non-blocking
    4. promise: a promise is a kind of EventEmitter which emits either “success” or “error”. (But not both.)

1. 从http://ctags.sourceforge.net/ 下载最新版本的ctags文件,目前为ctags-5.9.tgz.

2. 解压缩 tar -xf ctags-5.8.tgz

3. 安装: cd ctags-x-x && configure && make && make install

    这里需要注意一下,ctags默认会安装到/usr/local/bin/目录下,当你安装完毕后执行ctags命令,可能仍然执行的是Unix系统自带的那个ctags,而非你新安装的这个exuberant ctags,我的解决思路是,在 .bash_profile里创建一个名为 ectags的alias,指向我们新安装的这个exuberant ctags。

4. cd <工程目录>,  ectags -R, 就会自动生成tag文件了。

 

注:本文假设你对CucumberSelenium-WebDriver有一定的了解。

####什么是回归测试? 回归测试(regression test)是QA对程序功能问题的验证,通常我们的做法是:
QA 手工测试 -> 报告bug -> Dev 修bug -> 提交到代码库 -> 构建程序包 -> 部署-> QA手工测试 -> 报告bug, 如此反复…

试想随着版本需求范围的增加,回归测试的测试用例也会如滚雪球般越积越多,在实施回归测试的过程中,因此,手工测试对于QA来说是重复的、乏味的。

让我们来看一下回归测试的定义:

回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误。
回归测试的目的是,通过了回归测试的软件,至少其基本功能是可用的。

####回归测试应该覆盖哪些内容? 从上面的定义,我们就可以识别出哪些测试用例需要被包括到回归测试中:

  1. 与外部件集成的功能。
  2. 主干功能。
  3. 容易被break的测试。

####自动化回归测试 既然测试范围已经确定了,接下来就要考虑如何减少这些重复的工作。我们很自然地想到了自动化。随之而来的问题是:

  1. 我们如何实施自动化?
  2. 我们的项目时间很紧,如何在不影响项目进度的前提下实现自动化的回归测试?
  3. 对于web项目,如何保证其在各个浏览器下面都能够正常工作?

对于第一、三个问题,有个成熟的方案,selenium-webdriver,其支持多种语言API, 包括Java,C#,Python,Perl,PHP,Ruby。也支持对多种浏览器的调用,可以模拟多种浏览器下对app的访问,并且支持对结果页面进行检查。 对于第二个问题,在经历过几次不是很成功的实践之后,我个人很反对对项目采用”休克疗法”,即完全停止目前的工作,采用另外一种看起来更好的方式来解决当前的问题,最具代表性的例子就是”打着重构的幌子进行重写”,在所谓的”重构”完成之前,项目其他成员的工作都是被阻塞住的,而且一旦”重构”失败,也难以恢复到”重构”前的状态。

在这里,我推荐一种循序渐进的方式逐步实现回归测试的自动化。
举个例子: 在确定了回归测试的范围后,我需要测试一个网上书店的从最新书籍列表进入书籍详情页面的功能,我们分别用普通的测试用例和DSL描述的用例:

普通的测试用例:
预置条件
数据库中有三本书,其信息如下:

          ISBN          书名                       作者            价格 
         111111    Head First Design Pattern      Somebody         12
         222222    Test Driven Development       Kent Beck         22
         333333         Refactor                 Martin Fowler     20

操作步骤:
进入书籍列表页面,点击书籍”Head First Design Pattern”的链接
预期结果:
进入书籍《Head First Design Pattern》详情页面,能够正确展示书籍名,价格,作者

使用Cucumber DSL描述的测试用例:

    Given There are books as follows : 
        | ISBN   | 书名                      | 作者          | 价格 |
        | 111111 | Head First Design Pattern | Somebody      | 12   |
        | 222222 | Test Driven Development   | Kent Beck     | 22   |
        | 333333 | Refactor                  | Martin Fowler | 20   |
     And I am on the book list page
     And I follow "Head First Design Pattern"
     Then I should be on book detail page
     And I should see "Head First Design Pattern" as title
     And I should see "12" as price
     And I should see "somebody" as author</span>

####渐进式实现自动化回归测试 比较这两种形式的测试用例,我们排除语言实现的差异(中文和英文,而且Cucumber也是支持中文DSL的), 它们的共同点是,都是人类可以理解的语言,任何一个QA都能够编写上述两种形式的测试用例。不同点在于,使用Cucumber DSL描述的测试用例可以在以后的某个时间点很容易地转换成自动化测试用例.

于是,我们可以在项目中采取如下形式逐步把回归测试自动化:
阶段一: 确定回归测试覆盖功能点的范围, 使用cucumber DSL描述测试用例 手工执行这些用例 阶段二: 利用项目间歇期,把这些使用cucumber DSL描述的测试用例转换成自动化测试。 此时,项目中回归测试会存在自动化和手工测试两种形式,部分地节省了人力。 阶段三 : 所有的回归测试用例都被实现为自动化测试。 这些测试都是可重复的,可以大大节省QA手工执行测试用例的时间。 后期对回归测试用例的修改都相应地将其自动化。 就像重构一样,你可以在上面这三个阶段中的任何一个时刻停止,你也可以在停止之后继续。 如果你能够在你的项目里实践到阶段三,那么恭喜你,你们已经做到了让合适的人做合适的事情了。

    所谓“闭包”,就是在构造函数体内定义另外的函数作为目标对象的方法函数,而这个对象的方法函数反过来引用外层函数体中的临时变量。这使得只要目标对象在生存期内始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目标对象的方法内却始终能引用到该变量的值,而且该值只能通这种方法来访问。即使再次调用相同的构造函数,但只会生成新对象和方法,新的临时变量只是对应新的值, 和上次那次调用的是各自独立的

 分享了个关于closure的ppt在这里