QA seven's blog


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

前端测试进化论

发表于 2016-01-25

前端测试进化论

第一篇web 发展史

这篇主要是帮助我们qa 了解前端的历史与现状。

测试工程师大部分时间可能关注的是feature正确性,而对于前端代码级别的测试基本没有关心过,随着各种前端框架在web和移动端大行其道,绽放各种高大上的功能时,我们渐渐的发现前端代码级别问题逐渐暴露出来,甚至超越后端之势。那么问题来了,为什么以前这种现象出现呢?
我们现来看看前端技术发展史。

##石器时代——开荒阶段

带有简单逻辑的界面

最早期的Web界面基本都是在互联网上使用,而且基本都是静态页面展示,人们浏览某些内容,填写几个表单并且提交。当时的界面以浏览为主,基本都是HTML代码,我们来看一个最简单的HTML文件:

1
2
3
4
5
6
7
8
9
<html>
 <head> 
<title>开荒时代</title> 
</head>
 <body> 
<h1>静态页面</h1>
 <p>这是个测试</p>
 </body>
 </html>

带有简单逻辑的界面

接下来我们发现使用一些工具可以帮助我们做一些业务处理,这时javascript到来了,开始大放异彩。这时的代码的组织比较简单,而且CSS的运用也是比较少的。譬如:下面这个文档将带有一段JavaScript代码,用于拼接两个输入框中的字符串,并且弹出窗口显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
    <head>
        <title>javascript时代</title>
    </head>
    <body>
        <input id="firstNameInput" type="text" />
        <input id="lastNameInput" type="text" />
        <input type="button" onclick="greet()" />
        <script language="JavaScript">
        function greet() {
            var firstName = document.getElementById("firstNameInput").value;
            var lastName = document.getElementById("lastNameInput").value;
            alert("Hello, " + firstName + "." + lastName);
        }
        </script>
    </body>
</html>

结合了服务端技术的混合编程

由于静态界面不能实现保存数据等功能,出现了很多服务端技术,早期的有CGI(Common Gateway Interface,多数用C语言或者Perl实现的),ASP(使用VBScript或者JScript),JSP(使用Java),PHP等等,Python和Ruby等语言也常被用于这类用途。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
<title>Using GET Method to Read Form Data</title>
</head>
<body>
<center>
<h1>Using GET Method to Read Form Data</h1>
<ul>
<li><p><b>First Name:</b>
<%= request.getParameter("first_name")%>
</p></li>
<li><p><b>Last Name:</b>
<%= request.getParameter("last_name")%>
</p></li>
</ul>
</body>
</html>

铁器时代—Ajax席卷全世界

AJAX

由于Ajax的出现,规模更大,效果更好的Web程序逐渐出现,在这些程序中,JavaScript代码的数量迅速增加。出于代码组织的需要,“JavaScript框架”这个概念逐步形成。

JavaScript基础库—jquery

Prototype框架主要是为JavaScript代码提供了一种组织方式,对一些原生的JavaScript类型提供了一些扩展,比如数组、字符串,又额外提供了一些实用的数据结构,如:枚举,Hash等,除此之外,还对dom操作,事件,表单和Ajax做了一些封装。

Mootools框架的思路跟Prototype很接近,它对JavaScript类型扩展的方式别具一格,所以在这类框架中,经常被称作“最优雅的”对象扩展体系。
从这两个框架的所提供的功能来看,它们的定位是核心库,在使用的时候一般需要配合一些外围的库来完成。

jQuery与这两者有所不同,它着眼于简化DOM相关的代码。

DOM的选择

jQuery提供了一系列选择器用于选取界面元素,在其他一些框架中也有类似功能,但是一般没有它的简洁、强大。

1
2
3
4
5
$("*")              //选取所有元素
$("#lastname")      //选取id为lastname的元素
$(".intro")         //选取所有class="intro"的元素
$("p")              //选取所有&lt;p&gt;元素
$(".intro.demo")    //选取所有 class="intro"且class="demo"的元素

链式表达式

在jQuery中,可以使用链式表达式来连续操作dom,比如下面这个例子:

1
$("p.neat").addClass("ohmy").show("slow");

除此之外,jQuery还提供了一些动画方面的特效代码,也有大量的外围库,比如jQuery UI这样的控件库,jQuery mobile这样的移动开发库等等。

模块代码加载方式–Requirejs

以上这些框架提供了代码的组织能力,但是未能提供代码的动态加载能力。动态加载JavaScript为什么重要呢?因为随着Ajax的普及,jQuery等辅助库的出现,Web上可以做很复杂的功能,因此,单页面应用程序(SPA,Single Page Application)也逐渐多了起来。

单个的界面想要做很多功能,需要写的代码是会比较多的,但是,并非所有的功能都需要在界面加载的时候就全部引入,如果能够在需要的时候才加载那些代码,就把加载的压力分担了,在这个背景下,出现了一些用于动态加载JavaScript的框架,也出现了一些定义这类可被动态加载代码的规范。

在这些框架里,知名度比较高的是RequireJS,它遵循一种称为AMD(Asynchronous Module Definition)的规范。
例如下列是一个js代码保存为adder.js文件

1
2
3
4
5
6
7
define(["math"], function(math) {
    return {
        addTen : function(x) {
            return math.add(x, 10);
        }
    };
});

当我们需要使用这个模块时,我们只需引入就好

1
2
3
4
5
6
<script src="require.js"></script>
<script>
    require(["adder"], function(adder) {
        //使用这个adder
    });
</script>

RequireJS除了提供异步加载方式,也可以使用同步方式加载模块代码。AMD规范除了使用在前端浏览器环境中,也可以运行于nodejs等服务端环境,nodejs的模块就是基于这套规范定义的。(修订,这里弄错了,nodejs是基于类似的CMD规范的)

工业革命—前端框架

这个时期,随着Web端功能的日益复杂,人们开始考虑这样一些问题:

• 如何更好地模块化开发

• 业务数据如何组织

• 界面和业务数据之间通过何种方式进行交互

在这种背景下,出现了一些前端MVC、MVP、MVVM框架,我们把这些框架统称为MV*框架。这些框架的出现,都是为了解决上面这些问题,具体的实现思路各有不同,主流的有Backbone,AngularJS,Ember,Spine等等,本文主要选用Backbone和AngularJS来讲述以下场景。

数据模型

在这些框架里,定义数据模型的方式与以往有些差异,主要在于数据的get和set更加有意义了,比如说,可以把某个实体的get和set绑定到RESTful的服务上,这样,对某个实体的读写可以更新到数据库中。另外一个特点是,它们一般都提供一个事件,用于监控数据的变化,这个机制使得数据绑定成为可能。
在一些框架中,数据模型需要在原生的JavaScript类型上做一层封装,比如Backbone的方式是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var Todo = Backbone.Model.extend({
    // Default attributes for the todo item.
    defaults : function() {
        return {
            title : "empty todo...",
            order : Todos.nextOrder(),
            done : false
        };
    },
 
    // Ensure that each todo created has `title`.
    initialize : function() {
        if (!this.get("title")) {
            this.set({
                "title" : this.defaults().title
            });
        }
    },
 
    // Toggle the 'done' state of this todo item.
    toggle : function() {
        this.save({
            done : !this.get("done")
        });
    }
});

控制器

在Backbone中,是没有独立的控制器的,它的一些控制的职责都放在了视图里,所以其实这是一种MVP(Model View Presentation)模式,而AngularJS有很清晰的控制器层。

还是以这个todo为例,在AngularJS中,会有一些约定的注入,比如$scope,它是控制器、模型和视图之间的桥梁。在控制器定义的时候,将$scope作为参数,然后,就可以在控制器里面为它添加模型的支持。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function TodoCtrl($scope) {
    $scope.todos = [{
        text : 'learn angular',
        done : true
    }, {
        text : 'build an angular app',
        done : false
    }];
 
    $scope.addTodo = function() {
        $scope.todos.push({
            text : $scope.todoText,
            done : false
        });
        $scope.todoText = '';
    };
 
    $scope.remaining = function() {
        var count = 0;
        angular.forEach($scope.todos, function(todo) {
            count += todo.done ? 0 : 1;
        });
        return count;
    };
 
    $scope.archive = function() {
        var oldTodos = $scope.todos;
        $scope.todos = [];
        angular.forEach(oldTodos, function(todo) {
            if (!todo.done)
                $scope.todos.push(todo);
        });
    };
}

视图

在这些主流的MV*框架中,一般都提供了定义视图的功能。在Backbone中,是这样定义视图的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// The DOM element for a todo item...
var TodoView = Backbone.View.extend({
    //... is a list tag.
    tagName : "li",
 
    // Cache the template function for a single item.
    template : _.template($('#item-template').html()),
 
    // The DOM events specific to an item.
    events : {
        "click .toggle" : "toggleDone",
        "dblclick .view" : "edit",
        "click a.destroy" : "clear",
        "keypress .edit" : "updateOnEnter",
        "blur .edit" : "close"
    },
 
    // The TodoView listens for changes to its model, re-rendering. Since there's
    // a one-to-one correspondence between a **Todo** and a **TodoView** in this
    // app, we set a direct reference on the model for convenience.
    initialize : function() {
        this.listenTo(this.model, 'change', this.render);
        this.listenTo(this.model, 'destroy', this.remove);
    },
 
    // Re-render the titles of the todo item.
    render : function() {
        this.$el.html(this.template(this.model.toJSON()));
        this.$el.toggleClass('done', this.model.get('done'));
        this.input = this.$('.edit');
        return this;
    },
 
    //......
 
    // Remove the item, destroy the model.
    clear : function() {
        this.model.destroy();
    }
});

参考文献:

http://any9.com/1524.html
http://blog.jobbole.com/45169/
http://blog.jobbole.com/45170/
http://blog.jobbole.com/41988/
我毕竟不是一个前端dev,有所疏漏尽请原谅,还有转载和抄录了一些人的blog 也请大家谅解,只是想写一些前端的文章给qa用。

gulp_for_qa

发表于 2016-01-25

gulp 介绍

gulp

什么是gulp?这和我们qa有什么关系?

Gulp.js 是一个自动化构建工具,开发者可以使用它在项目开发过程中自动执行常见任务。
入门指南

  1. 全局安装 gulp:

    1
    $ npm install --global gulp
  2. 作为项目的开发依赖(devDependencies)安装:

    1
    $ npm install --save-dev gulp
  3. 在项目根目录下创建一个名为 gulpfile.js 的文件:

    1
    2
    3
    4
    5
    var gulp = require('gulp');
    gulp.task('default', function() {
    // 将你的默认的任务代码放在这
    });
  4. 运行 gulp:

1
$ gulp

默认的名为 default 的任务(task)将会被运行,在这里,这个任务并未做任何事情。
想要单独执行特定的任务(task),请输入 gulp 。

官网:
http://www.gulpjs.com.cn/docs/getting-started/

gulp api 非常简单,只有5个task,run,watch,src,和dest.

是的 你只需要搞懂这5个api 完全够用。

Gulp 能帮助我们更好更快的进行测试,制定标准等工作。

eslint

Javascript代码验证工具,这种工具可以检查你的代码并提供相关的代码改进意见
最大卖点,可以通过插件实现自定义规则

例如 gc 这边用的angluar

https://www.npmjs.com/package/eslint-plugin-angular

使用npm 安装jslint 插件

1
$ npm install gulp-eslint --save-dev

配置 .eslintrc文件

设置js规范的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"env": {
"browser": true,
},
"globals": {
"angular": true,
},
"rules": {
"camelcase": 2,
"curly": 2,
"brace-style": [2, "1tbs"],
"quotes": [2, "single"],
"semi": [2, "always"],
"space-in-brackets": [2, "never"],
"space-infix-ops": 2,
}
}

配置 gulpfile

1
2
3
4
5
6
7
8
9
var eslint = require('gulp-eslint')
gulp.task('lint', function() {
return gulp.src(path.JS)
.pipe(eslint())
.pipe(eslint.format());
});
$ gulp lint

参考:

运行 gulp lint 命令

http://www.jianshu.com/p/c599185a0d84

jshint jslint jscs eslint的对比

http://developer.51cto.com/art/201506/481510.htm
http://www.sitepoint.com/comparison-javascript-linting-tools/

##browersync

Browsersync能让浏览器实时、快速响应您的文件更改(html、js、css、sass、less等)并自动刷新页面。更重要的是 Browsersync可以同时在PC、平板、手机等设备下进项调试。您可以想象一下:“假设您的桌子上有pc、ipad、iphone、android等设备,同时打开了您需要调试的页面,当您使用browsersync后,您的任何一次代码保存,以上的设备都会同时显示您的改动”。无论您是前端还是后端工程师,使用它将提高您30%的工作效率。

1
$ npm install browser-sync gulp --save-dev

配置gulpfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var path = {
HTML : "html/*.html",
LESS : "less/*.less",
CSS : "css/*.css",
JS : "js/*.js"
};
gulp.task("serve", ["less", "js-watch", "html"], function() {
browserSync.init({
server : "./"
});
gulp.watch(path.LESS, ["less"]);
gulp.watch(path.JS, ["js-watch"]);
gulp.watch(path.HTML, ["html"]);
gulp.watch(path.HTML).on("change", function() {
browserSync.reload;
});
});
gulp.task("less", function() {
gulp.src(path.LESS)
.pipe(less())
.pipe(gulp.dest(path.CSS))
.pipe(browserSync.stream());
})
gulp.task("js-watch", function() {
gulp.src(path.JS)
.pipe(browserSync.stream());
})
gulp.task("html", function() {
gulp.src(path.HTML)
.pipe(browserSync.stream());
})

运行命令
就可以看到代码变更对不同浏览器页面造成的影响了。
image

参考文档

http://www.browsersync.cn/docs/gulp/

开启代理模式

1
2
3
browser-sync start --proxy "http://localhost:3000/products/voip-phones/yealink-t20p" --host "http://localhost:3001/products/voip-phones/yealink-t20p"
gulp-istanbul

##Istanbul 是 JavaScript 程序的代码覆盖率工具
• 行覆盖率(line coverage):是否每一行都执行了?

• 函数覆盖率(function coverage):是否每个函数都调用了?

• 分支覆盖率(branch coverage):是否每个if代码块都执行了?

• 语句覆盖率(statement coverage):是否每个语句都执行了?

配置gulpfile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gulp.task('pre-test', function () {
return gulp.src(['js/*.js'])
// Covering files
.pipe(istanbul())
// Force `require` to return covered files
.pipe(istanbul.hookRequire());
});
gulp.task('test', ['pre-test'], function () {
return gulp.src(['test/*.js'])
.pipe(mocha())
// Creating the reports after tests ran
.pipe(istanbul.enforceThresholds({ thresholds: { global: 90 } }))
.pipe(istanbul.writeReports({
dir: './assets/unit-test-coverage',
reporters: [ 'lcov' ],
reportOpts: { dir: './assets/unit-test-coverage'} }));
});

运行gulp 命令

image1

####参考:
http://www.ruanyifeng.com/blog/2015/06/istanbul.html

http://blog.oskoui-oskoui.com/?p=8478

https://github.com/SBoudrias/gulp-istanbul

##所有例子用的repo
https://github.com/qileilove/gulp_qa

1…45
seven

seven

小qa 在thoughtworks苦苦挣扎中

42 日志
16 标签
RSS
© 2017 seven
由 Hexo 强力驱动
主题 - NexT.Pisces