脚手架vue-cli系列五:基于Nightwatch的端到端测试环境
不同公司和组织之间的测试效率迥异。在这个富交互和响应式处理随处可见的时代,很多组织都使用敏捷的方式来开发应用,因此测试自动化也成为软件项目的必备部分。测试自动化意味着使用软件工具来反复运行项目中的测试,并为回归测试提供反馈。
端到端测试又简称E2E(End-To-End test)测试,它不同于单元测试侧重于检验函数的输出结果,端到端测试将尽可能从用户的视角,对真实系统的访问行为进行仿真。对于Web应用来说,这意味着需要打开浏览器、加载页面、运行JavaScript,以及进行与DOM交互等操作。简言之,单元测试的功能只能确保单个组件的质量,无法测试具体的业务流程是否运作正常,而E2E却正好与之相反,它是一个更高层次的面对组件与组件之间、用户与真实环境之间的一种集成性测试 。
E2E测试的意义在于可以通过程序固化和仿真用户操作,对于开发人员而言,基于E2E测试能极大地提高Web的开发效能,节约开发时间。
先来看看如果没有E2E测试下的一次从开发到手工测试成功的过程:
这个过程还属于简化过的,还没有包括在观察结果时要打开浏览器的调试窗口观看某些内部的运行变量或者网页代码结构。整个过程都是纯人工操作,人工操作最大的问题是一个程序可能要调试好几次,同样的操作就要重复数遍。即使有严格的规定,程序员们大多都还是随便地做“通过”式操作,尤其在输入样本数据时,绝大多数的程序员几乎都是乱输,出现得最多的就是各种随意的数字或者是“aaa”、“asd”、“aws”这样毫无意义的字符。以这种方式开发出来的程序在验收时产品经理或者客户会经常说一句话:“我上次试过是没有问题的!”这样的失误归根结底不在程序员本身,因为这是一种人性!一个人如果重复多次自己都觉得毫无意义的动作时,要不就逃避不做,如果不能逃避就会消极对待。
所以我们应该用更高效、更能弥补人性化缺陷和更有意义的办法来处理,这就是E2E测试,先来看看如果使用E2E测试后的开发过程将会变成什么:
从运行测试开始,所有的一切都是自动的!这就是最大的区别,还有更重要的一点是,当我们要写出E2E测试时就需要对操作需求有深刻的理解,在这一过程中还有很大的机会对用户的操作进行优化,从而提高用户体验。
Nightwatch
vue-cli的webpack模板也为我们准备了一个当下很流行的E2E测试框架——Nightwatch。
Nightwatch是一套新近问世的基于Node.js的验收测试框架,使用Selenium WebDriver API以将Web应用测试自动化。它提供了简单的语法,支持使用JavaScript和CSS选择器来编写运行在Selenium服务器上的端到端测试。
这个框架在配置好后的具体工作流程如下图所示。
Nightwatch采用Fluent interface模式(https://en.wikipedia.org/wiki/Fluent_interface)来简化端到端测试的编写,语法非常简洁易懂,正如以下代码所示。
this.demoTestGoogle = function (browser) { browser .url('http://www.google.com') .waitForElementVisible('body', 1000) .setValue('input[type=text]', 'nightwatch') .waitForElementVisible('button[name=btnG]', 1000) .click('button[name=btnG]') .pause(1000) .assert.containsText('#main', 'The Night Watch') .end(); }
我们可以从Nightwatch网站找到当前提供特性的列表:
● 简单但强大的语法。只需要使用JavaScript和CSS选择器,开发者就能够非常迅捷地撰写测试。开发者也不必初始化其他对象和类,只需要编写测试规范即可。
● 内建命令行测试运行器,允许开发者同时运行全部测试——分组或单个运行。
● 自动管理Selenium服务器;如果Selenium运行在另一台机器上,那么也可以禁用此特性。
● 支持持续集成:内建JUnit XML报表,因此开发者可以在构建过程中,将自己的测试与系统(例如Hudson或Teamcity等)集成。
● 使用CSS选择器或Xpath,定位并验证页面中的元素或是执行命令。
● 易于扩展,便于开发者根据需要,实现与自己应用相关的命令。
配置 Nightwatch
要了解Nightwatch的配置和用法,先从工程结构入手。
工程结构
. └── test └── e2e ├── custom-assertions // 自定义断言 │ └── elementCount.js ├── page-objects // 页面对象文件夹 ├── reports // 输出报表文件夹 ├── screenshots // 自动截屏 ├── nightwatch.conf.js // nightwatch 运行配置 ├── runner.js // 运行器 └── specs // 测试文件 └── test.spec.js
以上是vue-cli为我们自动创建的Nightwatch工程结构,specs是测试文件存放的文件夹,nightwatch.conf.js是Nightwatch的运行配置文件。其他的目录将会在具体的章节逐一地进行讲述。
基本配置
Nightwatch的配置项都集中在nightwatch.conf.js中,其实这个配置也可以是一个JSON格式,采用JSON格式只需要简单地对配置项写入一些常量即可。但使用模块的方式进行配置可以执行一些额外的配置代码,这样则显得更为灵活。以下是我调整过的nightwatch.conf.js文件内容:
require('babel-register'); var config = require('../../config'); var seleniumServer = require('selenium-server'); var phantomjs = require('phantomjs-prebuilt'); module.exports = { "src_folders": ["test/e2e/specs"], "output_folder": "test/e2e/reports", "custom_assertions_path": ["test/e2e/custom-assertions"], "page_objects_path": "test/e2e/page-objects", "selenium": { "start_process": true, "server_path": seleniumServer.path, "port": 4444, "cli_args": { "webdriver.chrome.driver": require('chromedriver').path } }, "test_settings": { "default": { "selenium_port": 4444, "selenium_host": "localhost", "silent": true, launch_url:"http://localhost:" + (process.env.PORT || config.dev.port), "globals": { } }, "chrome": { "desiredCapabilities": { "browserName": "chrome", "javascriptEnabled": true, "acceptSslCerts": true } }, "firefox": { "desiredCapabilities": { "browserName": "firefox", "javascriptEnabled": true, "acceptSslCerts": true } } } }
Nightwatch的配置分为以下三类:
● 基本配置;
● Selenium配置;
● 测试环境配置。
在配置模块中的所有根元素配置项都属于基本配置,用于控制Nightwatch的全局性运行的需要。下表为Nightwatch的基本配置项的详细说明。
Selenium 配置
Selenium是一组软件工具集,每一个工具都有不同的方法来支持测试自动化。大多数使用Selenium的QA工程师只关注一两个最能满足他们项目需求的工具。然而,学习所有的工具你将有更多选择来解决不同类型的测试自动化问题。这一整套工具具备丰富的测试功能,很好地契合了测试各种类型的网站应用的需要。这些操作非常灵活,有多种选择来定位UI元素,同时将预期的测试结果和实际的行为进行比较。Selenium一个最关键的特性是支持在多浏览器平台上进行测试。
Selenium诞生于2004年,当在ThoughtWorks工作的Jason Huggins在测试一个内部应 用时,作为一个聪明的家伙,他意识到相对于每次改动都需要手工进行测试,他的时间应该用得更有价值。他开发了一个可以驱动页面进行交互的JavaScript库,能让多浏览器自动返回测试结果。那个库最终变成了Selenium的核心,它是Selenium RC(远程控制)和Selenium IDE所有功能的基础。Selenium RC是开拓性的,因为没有其他产品能让你使用自己喜欢的语言来控制浏览器。
Selenium是一个庞大的工具,所以它也有自己的缺点。由于它使用了基于JavaScript的自动化引擎,而浏览器对JavaScript又有很多安全限制,有些事情就难以实现。更糟糕的是,网站应用正变得越来越强大,它们使用了新浏览器提供的各种特性,都使得这些限制让人痛苦不堪。在2006年,一名Google的工程师Simon Stewart开始基于这个项目进行开发,这个项目被命名为WebDriver。此时,Google早已是Selenium的重度用户,但是测试工程师们不得不绕过它的限制。Simon需要一款能通过浏览器和操作系统的本地方法直接和浏览器进行通话的测试工具,来解决JavaScript环境沙箱的问题。WebDriver项目的目标就是要解决Selenium的痛点。
Selenium 1 (又叫Selenium RC或Remote Control)在很长一段时间内,Selenium RC都是最主要的Selenium项目,直到WebDriver和Selenium合并而产生了最新且最强大的Selenium 2。Seleinum 1仍然被活跃地支持着(更多是维护),并且提供一些Selenium 2短时间内可能不会支持的特性,包括对多种语言的支持(Java、JavaScript、Ruby、PHP、Python、Perl和C#)和对大多数浏览器的支持。
Selenium 2 (又叫Selenium WebDriver)代表了这个项目未来的方向,也是最新被添加到Selenium工具集中的。这个全新的自动化工具提供了很多了不起的特性,包括更内聚和面向对象的API,并且解决了旧版本限制。Selenium和WebDriver的作者都赞同两者各具优势,而两者的合并使得这个自动化工具更加强健。Selenium 2.0正是于此的产品。它支持WebDriver API及其底层技术,同时也在WebDriver API底下通过Selenium 1技术为移植测试代码提供极大的灵活性。此外,为了向后兼容,Selenium 2仍然使用Selenium 1的Selenium RC接口。
你可以到http://selenium-release.storage.googleapis.com/index.html下载Selenium的各个稳定版本。
在Vue项目中如果使用vue-cli,那么Nightwatch将不需要进行任何的附加配置,否则你需要在命令行内安装Selenium的包装类库:
$ npm i selenium-server -D
Nightwatch能引导Selenium的启动,实际上我们并没有必要去修改Selenium服务器的默认运行配置,在nightwatch.conf.js配置文件中只需要声明Selenium服务器的二进制执行 文件的具体路径即可,这个可以从selenium-server包提供的Selenium包装对象的path属性中获取,而无须将本机的物理路径写死到配置文件内。
var seleniumServer = require('selenium-server'); module.exports= { "selenium": { "start_process": true, "server_path": seleniumServer.path, "port": 4444, "cli_args": { "webdriver.chrome.driver": require('chromedriver').path } }, // ... 省略 }
以下是Selenium的详细配置项说明:
cli_args 的配置
● webdriver.firefox.profile:Selenium默认为每个会话创建一个独立的Firefox配置方案。如果你希望使用新的驱动配置可以在此进行声明。
● webdriver.chrome.driver:Nightwatch同样可以使用Chrome浏览器加载测试,当然你要先下载一个ChromeDriver的二进制运行库对此进行支持。此配置项用于指明ChromeDriver的安装位置。除此之外,还需要在test_settings配置内使用desiredCapabilities对象为Chrome建立配置方案。
● webdriver.ie.driver:Nightwatch也支持IE,其作用与用法与Chrome相同,此处则不过多赘述。
测试环境配置
test_settings内的项目将应用于所有的测试实例,在E2E测试中我们可以通过Nightwatch提供的默认实例对象browser获取这些配置值,vue-cli为我们创建了default、firefox和chrome三个环境配置项,default配置是应用于所有环境的基础配置选项,其他的配置项会自动覆盖与default相同的配置值。
firefox和chrome这两个配置项是对两种浏览器的驱动进行描述和配置。对于其他语言或框架而言它们也是常客,但由于性能太低,在实战中通常只是个摆设,下文中我将会介绍一种实战效率更高的无头浏览器PhantomJS,对其取而代之。
不要被vue-cli创建默认配置所迷惑,test_settings并不单单只是对浏览器的一些基本运行参数的配置,它正确的用法是对E2E测试环境的配置。单元测试只能运行于开发环境内,而E2E却可以运行于本地环境与网络环境,更准确地说是开发环境与生产环境。所以这个配置项可以用以下的方式进行设置:
"test_settings": { "default": { "selenium_port": 4444, "selenium_host": "localhost", "silent": true, launch_url:"http://localhost:" + (process.env.PORT || config.dev.port), "globals": {} }, "dev": { "desiredCapabilities": { "browserName": "chrome", "javascriptEnabled": true, "acceptSslCerts": true } }, "production": { "launch_url":"http://www.your-domain.com" "desiredCapabilities": { "browserName": "firefox", "javascriptEnabled": true, "acceptSslCerts": true } } }
虽然与原有的配置只是在用词上做了一点点改变,但用词的改变将会彻底地改变我们对其的认知与思路!
下表是测试环境配置项的详细说明:
执行 E2E 测试
vue-cli已经在package.json中配置了运行测试的指令:
$ npm run e2e
这个指令是默认启用Chrome运行环境的,如果指定运行环境可使用--env选项:
$ npm run e2e --env
使用无头浏览器 PhantomJS
vue-cli webpack脚手架模板非常好用,它将环境的复杂性降低了很多,但是却没有很好地诠释它里面采用的每个模块的理由和功能,以及它们的使用特点。这对于入门者来说确实是将门槛降到最低点,但从工程化开发的角度来说,只知道有这些环境或者工具的存在是远远不够的,在Nightwatch中就埋了一个这样的坑。
我们的开发环境在配置Mocha和Karma时就已经安装了PhantomJS,但如果你细读Nightwatch的默认配置会惊奇地发现根本没有采用PhantomJS,只是配置了Chrome和Firefox!问题何在?一个字:慢!
Chrome的启动是很慢的,我们做E2E这种自动化测试如果用真实浏览器的话只能将性能拖下来,生命不能耗费在毫无意义的等待中!所以我们才会选择PhantomJS!没有默认配置PhantomJS作为主浏览器是这个环境的最大败笔。
办法总比问题多,所以如果没有,我们还可以自己动手来配置,其实方法也很简单。打开nightwatch.conf.js,在test_settings配置段的下方加入以下的内容:
"test_settings": { "default": { // ... } }, "phantom":{ "desiredCapabilities": { "browserName": "phantomjs", "javascriptEnabled": true, "acceptSslCerts": true, "phantomjs.page.settings.userAgent" : "Mozilla/5.0 (Macintosh; Intel MacOS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", "phantomjs.binary.path":"node_modules/phantomjs-prebuilt/bin/phantomjs" } }
Nightwatch是通过Selenium加载一个GhostDriver来引导PhantomJS浏览器的,上面的内容就相当于告诉Selenium加载一个GhostDriver,可执行程序则指向npm上安装的phantomjs-prebuilt包,再通过这个包来引导安装在本机上的PhantomJS启动。
按上文这样来引用PhantomJS的二进制程序的地址非常难看,还有原生配置中的Selenium执行程序地址也是一样的,这里介绍一个更DRY的方法来处理这些路径:
var seleniumServer = require('selenium-server'); var phantomjs = require('phantomjs-prebuilt'); module.exports = { // ...省略 "selenium": { // ... 省略 "server_path": seleniumServer.path, }, "test_settings": { // ... 省略 "phantom": { "desiredCapabilities": { // ... 省略 "phantomjs.binary.path": phantomjs.path } } // ... 省略 } }
做完这个简单的优化后就可以打开runner.js文件找到:
if (opts.indexOf('--env') === -1) { opts = opts.concat(['--env', 'chrome']) }
将chrome改为phantom就行了:
if (opts.indexOf('--env') === -1) { opts = opts.concat(['--env', 'phantom']) }
重新加载测试程序,在同一台iMac上的运行速度直接降到了5秒,测试运行速度提升了3倍!如果你有配置更好的机器,将硬盘换成SSD之后会有更惊人的速度。
Nightwatch 与 Cucumber
如果你正在开发的项目的业务复杂性不大,可以直接使用Nightwatch推荐的链式调用写法。但是当这种做法真正应用在业务流程较多,或者业务操作相对复杂的应用场景时,你会觉得总有写不完的E2E测试,因为这么做E2E测试是没有办法一次性覆盖所有需求的!
E2E测试其实是行为式驱动开发的实现手法,如果跳过了行为式驱动开发的分析部分直接编写E2E,其结果只能是写出一堆严重碎片化的测试场景,甚至会出现很多根本不应该出现的操作。
幸好Nightwatch具有很好的扩展性与兼容性,能集成最正统的BDD测试框架Cucumber(https://cucumber.io/)。Cucumber是原生于Ruby世界的BDD框架,但它也有很多的语言实现版本,我们可以安装一套专门为Nightwatch编写的Cucumber版本——nightwatch-cucumber(https://github.com/mucsi96/nightwatch-cucumber)。本章只介绍关于环境与工具的配置,而关于如何来应用BDD,内容已经超出了本书的知识范围,如果有兴趣的话可以参考《攀登架构之巅》一书中行为式驱动开发的章节内容。
$ npm i nightwatch-cucumber -D
然后在~/test/e2e/nightwatch.conf.js文件中加入对Cucumber的配置:
// ... 省略 require('babel-register'); require('nightwatch-cucumber')({ nightwatchClientAsParameter: true, featureFiles: ['test/e2e/features'], stepDefinitions: ['test/e2e/features/step_definitions'], jsonReport: 'test/e2e/reports/cucumber.json', htmlReport: 'test/e2e/reports/cucumber.html', openReport: false });