最受欢迎的 5 款 Node.js 端到端测试框架

shuq5646 8年前
   <p>测试,尤其是自动化测试在现代 WEB 工程中有着非常重要的角色,与交付过程集成良好的自动化测试流程可以在新版发布时帮你快速回归产品功能,也可以充当产品文档。测试因粒度不同又可以分为单元测试、接口测试、功能测试。在 WEB 领域,功能测试亦称为端到端测试( <a href="/misc/goto?guid=4959746222426755263" rel="nofollow,noindex">End to End Test</a> ,简称 E2E 测试),笔者在本文中会结合自身实践和 GitHub 趋势对比最受欢迎的 Node.js E2E 测试解决方案,首先我们按 GitHub 的 star 总数量排序,取前 5 名列举如下(注意:你阅读本文时 star 的数量可能已经不是最新的)。</p>    <ul>     <li>CasperJS — 6460 个 star, <a href="/misc/goto?guid=4958830653811784781" rel="nofollow,noindex">官网</a> , <a href="/misc/goto?guid=4959746222537756195" rel="nofollow,noindex">代码</a> ,最近更新于 7 天前;</li>     <li>Protractor — 6408 个 star, <a href="/misc/goto?guid=4959746222615385904" rel="nofollow,noindex">官网</a> , <a href="/misc/goto?guid=4959638392083193675" rel="nofollow,noindex">代码</a> ,最近更新于 10 天前;</li>     <li>Nightwatch.js — 6121 个 star, <a href="/misc/goto?guid=4958826167751782411" rel="nofollow,noindex">官网</a> , <a href="/misc/goto?guid=4959719509765812094" rel="nofollow,noindex">代码</a> ,最近更新于 6 天前;</li>     <li>TestCafe — 2268 个 star, <a href="/misc/goto?guid=4959746222791982971" rel="nofollow,noindex">官网</a> , <a href="/misc/goto?guid=4959746222874289947" rel="nofollow,noindex">代码</a> ,最近更新于 1 天前;</li>     <li>CodeceptJS — 1087 个 star, <a href="/misc/goto?guid=4959746222961356577" rel="nofollow,noindex">官网</a> , <a href="/misc/goto?guid=4959746223045794844" rel="nofollow,noindex">代码</a> ,最近更新于 7 天前;</li>    </ul>    <p>然后分别从环境搭建、测试编写、测试报告等方面来直观展示这 5 个 E2E 测试框架,期望能够对做测试框架选型的同学有帮助。为了更客观的体现各测试框架的特点,笔者设计了一些包含 E2E 测试中常用操作的测试用例,分别使用不同的框架来编写。E2E 测试的常用操作如下:</p>    <ul>     <li>打开网页,跳转网页:打开 Github 的首页;</li>     <li>填写输入框,提交表单:键入搜索词,提交搜索表单;</li>     <li>元素单击等操作:单击搜索结果的第一项;</li>     <li>元素数量、属性检视:确认搜索结果展示了 10 条;</li>     <li>页面运行环境检视:确认页面的地址是正确的;</li>    </ul>    <h3>CasperJS</h3>    <p><a href="/misc/goto?guid=4958830653811784781" rel="nofollow,noindex">CasperJS</a> 是 star 数最高的测试框架,也是笔者最早开始采用的 E2E 测试框架,使用 Python 编写,虽不算是严格意义上的原生 Node.js 解决方案,但因为能够使用 npm 安装,且能够很好的与 Node.js 工具链组合使用,笔者还是把它列在了这里。其特别之处在于只能与无界面浏览器(Headless Browser)组合使用,比如 <a href="/misc/goto?guid=4958827065804395825" rel="nofollow,noindex">PhantomJS</a> 和 <a href="/misc/goto?guid=4959645899362151212" rel="nofollow,noindex">SlimerJS</a> ,这也让 CasperJS 的优势显而易见:测试运行速度比真实浏览器快不少,且你不需要在持续集成系统中安装各种浏览器或者某个浏览器的不同版本;潜在的坑在于,无界面浏览器的表现有时和真实浏览器不完全相同,会带来某些难以排查解决的浏览器兼容问题。</p>    <p>安装步骤</p>    <ul>     <li>安装 Python 2.6 或更高版本</li>     <li>安装 PhantomJS:npm install -g phantomjs</li>     <li>安装 CasperJS:npm install -g casperjs</li>    </ul>    <p>编写测试</p>    <p>如果使用 ES6 之前的风格来编写 CasperJS 测试,代码看起来会显得非常臃肿,而实际上 CasperJS 也不支持任何 ES6/ES7 的新语法,除非你在运行测试之前自己对代码进行预编译,实际代码如下:</p>    <pre>  <code class="language-javascript">casper.test.begin('Github Search', function suite(test) {      casper.start('https://github.com', function () {    // 打开首页          test.assertVisible('.js-site-search-form', 'should search input visible');          this.fill('.js-site-search-form', { q: 'casperjs' }, true); // 键入搜索词、并提交      });        casper.then(function () {          test.assertEval(function() {    // 确认搜索结果是 10              return __utils__.findAll('.repo-list-item').length >= 10;          }, 'should show 10 results');      });        casper.then(function () {          this.click('.repo-list-item h3 a'); // 点击第1条结果      });        var location = null;        casper.then(function () {   // 这里是取环境变量          test.assertVisible('.repository-content', 'should repo detail visible');          location = this.evaluate(function () {              return window.location;          });      });        casper.then(function () {   // 确认目前跳转到了 casperjs 官方仓库          test.assertEquals(location.pathname, '/casperjs/casperjs', 'should casperjs repo found');      });        casper.run(function () {          test.done();      });  });</code></pre>    <p>因为 CasperJS 对 <a href="/misc/goto?guid=4958524591480500361" rel="nofollow,noindex">CoffeeScript</a> 有天然的支持,熟悉 CoffeeScript 的同学可以尝试使用 CoffeeScript 编写测试,或者使用 <a href="/misc/goto?guid=4959746223245077300" rel="nofollow,noindex">这个工具</a> 转换为 CoffeScript。</p>    <p>运行测试</p>    <pre>  <code class="language-javascript">casperjs test casperjs/test.js</code></pre>    <p>查看报告</p>    <p>测试通过</p>    <p><img src="https://simg.open-open.com/show/26a53cde5d9a1da669ba3b16b7b94e01.png"></p>    <p>测试失败</p>    <p><img src="https://simg.open-open.com/show/36e33dc81492a63523f3a1144f2229fb.png"></p>    <h3>Protractor</h3>    <p><a href="/misc/goto?guid=4959746222615385904" rel="nofollow,noindex">Protractor</a> 是 <a href="/misc/goto?guid=4958866372165218034" rel="nofollow,noindex">Angular</a> 官方正在使用的 E2E 测试框架,可以说是专门为 Angular 定制,内置了各种可以选择、操作 Angular 元素的便捷方法,如果你的应用基于 Angular 开发,使用它可以减少很多重复代码(显然类似的便利在其他框架中也有支持)。对于 Angular 的重度使用者,Protractor 会是非常明智的选择,不同于 CasperJS 的是 Protractor 在真实浏览器中运行测试代码。此外,Protractor 内置的页面加载等待的功能,在 CasperJS 中需要自己设置合理的超时。相比于本文列出的其他框架,Protractor 的明显优势是测试用例的组织方式可以自由使用 <a href="/misc/goto?guid=4959554590782762596" rel="nofollow,noindex">Jasmine</a> 或者 <a href="/misc/goto?guid=4958968603421901612" rel="nofollow,noindex">Mocha</a> 。</p>    <p>安装步骤</p>    <ul>     <li>安装 JDK</li>     <li>安装 Protractor:npm install –g protractor</li>     <li>初始化 WebDriver Manager:webdriver-manager update</li>     <li><a href="/misc/goto?guid=4959746223421622009" rel="nofollow,noindex">创建配置文件</a></li>    </ul>    <p>编写测试</p>    <p>Protractor 默认开启了等待 Angular 加载并初始化完成的功能,如果你测试的不是 Angular 应用,则需要关闭这个功能,测试代码示例如下:</p>    <pre>  <code class="language-javascript">describe('angularjs homepage todo list', function () {      browser.ignoreSynchronization = true;   // 不启用智能等待,因为 github 不是用 angluar 编写的      browser.get('https://github.com');        it('should search input visible', function () {          var searchInput = element(by.className('js-site-search-focus'));          var searchForm = element(by.className('js-site-search-form'));          expect(searchInput.isDisplayed()).toEqual(true);          searchInput.sendKeys('protractor');          searchForm.submit();      });        it('should show 10 results', function () {          var searchList = element.all(by.className('repo-list-item'));          expect(searchList.count()).toEqual(10);          element(by.css('.repo-list-item h3 a')).click();      });        it('should repo detail visible', function () {          var repoContent = element.all(by.className('repository-content'));          expect(repoContent.isDisplayed()).toEqual([true]);      });        it('should protractor repo found', function (done) {          browser.executeScript(function () {              return window.location;          }).then(function (location) {              expect(location.pathname).toEqual('/angular/protractor');              done();          });      });  });</code></pre>    <p>运行测试</p>    <ul>     <li>运行 WebDriver Manager: webdriver-manager start</li>     <li>运行测试:protractor protractor.config.js</li>    </ul>    <p>查看报告</p>    <p>测试通过</p>    <p><img src="https://simg.open-open.com/show/842d21bf8cbc0afcd3fe97316b67923b.png"></p>    <p>测试失败</p>    <p><img src="https://simg.open-open.com/show/f0ee7c1bbd1b0b7f80317f27c91fb4d3.png"></p>    <h3>Nightwatch</h3>    <p>同样流行的 <a href="/misc/goto?guid=4958826167751782411" rel="nofollow,noindex">Nightwatch</a> ,可以认为是 Protractor 的主要竞争对手,使用 Nigthwatch 编写的代码非常简洁,但是你需要手动在测试代码中添加合适的等待来保障测试的稳定,而 Protractor 和 TestCafe 则提供了内置的支持;Nightwatch 的主要劣势在于繁琐的安装步骤,可能部分同学看到这个 <a href="/misc/goto?guid=4959746223516897662" rel="nofollow,noindex">安装文档</a> 或者下面的安装步骤就知难而退了。</p>    <p>安装步骤</p>    <ul>     <li>安装 JDK,版本 7 以上</li>     <li>下载 Selenium: <a href="/misc/goto?guid=4959746223597968522" rel="nofollow,noindex">selenium-server-standalone-{VERSION}.jar</a> ,复制到测试目录</li>     <li>下载 <a href="/misc/goto?guid=4959746223683802905" rel="nofollow,noindex">WebDriver for Google Chrome</a> ,复制到测试目录</li>     <li>安装 Nightwatch: npm install -g nightwatch</li>     <li><a href="/misc/goto?guid=4959746223764608288" rel="nofollow,noindex">创建配置文件</a> ,需要在配置中声明 chromewebdriver 的地址;</li>    </ul>    <p>编写测试</p>    <pre>  <code class="language-javascript">module.exports = {      'Github Search': function (browser) {          browser // 打开首页、填写搜索词、提交搜索表单              .url('https://github.com')              .assert.visible('.js-site-search-focus', 'should search input visible')              .setValue('.js-site-search-focus', 'nightwatch')              .submitForm('.js-site-search-form')              .pause(1000);            browser.execute(function () {   // 确认展示 10 条搜索结果              return document.querySelectorAll('.repo-list-item').length;          }, function (args) {              browser.assert.equal(args.value, 10, 'should show 10 results');          });            browser.click('.repo-list-item h3 a').pause(1000);            browser.assert.visible('.repository-content', 'should repo detail visible');            browser.execute(function () {              return window.location;          }, function (args) {    // 确认打开了 nightwatch 官网              browser.assert.equal(args.value.pathname, '/nightwatchjs/nightwatch', 'should nightwatch repo found');          });            browser.end();      }  };</code></pre>    <p>运行测试</p>    <ul>     <li>运行 Selenium:java -jar selenium-server-standalone-3.0.0.jar</li>     <li>nightwatch test.js</li>    </ul>    <p>查看报告</p>    <p>测试通过</p>    <p><img src="https://simg.open-open.com/show/15fc7b89e72cfa49c69bf088439e0a22.png"></p>    <p>测试失败</p>    <p><img src="https://simg.open-open.com/show/92e112f14e0384794a117f02c7202a1b.png"></p>    <h3>TestCafe</h3>    <p><a href="/misc/goto?guid=4959746222791982971" rel="nofollow,noindex">TestCafe</a> 是非常年轻但很受开发者欢迎的测试框架,因为不需要依赖 <a href="/misc/goto?guid=4959638249352007937" rel="nofollow,noindex">WebDriver</a> 之类的东西,TestCafe 环境只需一键即可完成,这也意味着,你可以在任何安装了浏览器应用的物理设备上运行测试。TestCafe 对 ES6/ES7 语法的天然支持让它更具前瞻性,命令行工具产生的测试报告简洁但不失完整。由于开源的时间较短,相比于其他测试框架 TestCafe 的社区和生态还不够成熟。尽管如此,不断出现的各种 TestCafe 功能扩展也证明了它的社区和生态在不断壮大。对于站在 WEB 技术风口浪尖的同学,TestCafe 无疑是非常值得留意的 E2E 测试解决方案,开箱即用的特性极大的降低了使用者的成本。</p>    <p>安装步骤</p>    <p>npm install testcafe -g</p>    <p>编写测试</p>    <p>TestCafe 的测试组织方式详见 <a href="/misc/goto?guid=4959746223892688377" rel="nofollow,noindex">这里</a> , <a href="/misc/goto?guid=4959746223980189734" rel="nofollow,noindex">选择符支持</a> 也非常强大,支持类似于 jQuery 的灵活异步的选择符, <a href="/misc/goto?guid=4959746223245077300" rel="nofollow,noindex">断言风格</a> 非常类似 <a href="/misc/goto?guid=4958830654755354669" rel="nofollow,noindex">Chai</a> ,下面是测试代码:</p>    <pre>  <code class="language-javascript">import { Selector } from 'testcafe';    fixture `Github Search`      .page `https://github.com`;    test('should github search work as expected', async t => {      const searchInput = Selector('.js-site-search-focus');      const searchList = Selector('.repo-list-item');      const resultItem = Selector('.repo-list-item h3 a');      const repoContent = Selector('.repository-content');        await t.setTestSpeed(0.8);      await t.expect(searchInput.exists).eql(true, 'should search input visible');      await t.typeText(searchInput, 'testcafe');      await t.pressKey('enter');        await t.expect(searchList.count).eql(10, 'should show 10 results');      await t.click(resultItem);        await t.expect(repoContent.exists).eql(true, 'should repo detail visible');        const location = await t.eval(() => window.location);      await t.expect(location.pathname).eql('/DevExpress/testcafe', 'should testcafe repo found');  });</code></pre>    <p>运行测试</p>    <pre>  <code class="language-javascript">testcafe chrome testcafe/test.js</code></pre>    <p>查看报告</p>    <p>测试通过</p>    <p><img src="https://simg.open-open.com/show/4fcc1947bab50367edb4a1dab1229656.png"></p>    <p>测试失败</p>    <p><img src="https://simg.open-open.com/show/056523c79d14eae3bf663c69dd1d68e0.png"></p>    <h3>CodeceptJs</h3>    <p><a href="/misc/goto?guid=4959746222961356577" rel="nofollow,noindex">CodeceptJs</a> 可能并不算是严格意义的 E2E 测试框架,它对各种测试运行工具做了一层封装,旨在提供更简洁的 API,你可以自由选择下面这些测试运行工具:</p>    <ul>     <li>WebDriverIO</li>     <li>Protractor</li>     <li>Selenium WebDriver JS</li>     <li>NightmareJS</li>    </ul>    <p>CodeceptJs 让笔者比较欣赏的地方在于测试用例的组织,基于 Feature 和 Scenario 两个粒度来组织测试让它看起来更有 E2E 测试的样子,它支持最新的 ES6 语法,同时也屏蔽各种复杂的回调细节,所有的测试用例都是以第一人称来做,让测试代码阅读起来更像是自然语言,而让笔者担忧的地方在于,过多的封装可能导致出问题时排查比较困难。</p>    <p>安装步骤</p>    <ul>     <li>安装 CodeceptJs npm install -g codeceptjs</li>     <li>用命令行工具初始化配置 codecept.json:codeceptjs init</li>     <li>使用命令行工具生成测试:codeceptjs gt</li>     <li>此外,你需要安装你所选择的底层测试工具,比如 WebDriver、Protractor</li>    </ul>    <p>编写测试</p>    <pre>  <code class="language-javascript">Feature('Github Search');    Scenario('search codecept repo', (I) => {      I.amOnPage('https://github.com');      I.seeElement('.js-site-search-focus');      I.fillField('.js-site-search-focus', 'codeceptjs');      I.pressKey('Enter');        I.seeElement('.repo-list-item');      I.click('.repo-list-item h3 a');      I.seeElement('.repository-content');      I.seeInCurrentUrl('/Codeception/CodeceptJS');  });</code></pre>    <p>运行测试</p>    <p>codeceptjs run</p>    <p>查看报告</p>    <p>测试通过</p>    <p><img src="https://simg.open-open.com/show/2b9808f65ea7c8edbb5a6e37da2b9a64.png"></p>    <p>测试失败</p>    <p><img src="https://simg.open-open.com/show/3d03ad71fe0d1ab1df570cb809816a8e.png"></p>    <h2>总结对比</h2>    <p>本文中的所有代码可以在 GitHub <a href="/misc/goto?guid=4959746224112430725" rel="nofollow,noindex">仓库</a> 上看到。任何开发工具的演化都是朝着更快捷、高效的目标。本文介绍的几个 E2E 测试框架可以说各有所长,在做框架选型的时候该考虑哪些因子呢?这些因子的优先级如何?下面是笔者的考虑:</p>    <ul>     <li>上手简单:P0,环境搭建步骤?出错的概率?是否需要繁琐的配置?TestCafe 非常简单;</li>     <li>文档支持:P0,是否包含入门文档?API 参考?开发者文档?本文中的五款都还不错;</li>     <li>过程透明:P0,测试运行过程是否是透明的,能否观察到页面行为?CasperJS 就像个黑盒;</li>     <li>运行速度:P0,测试运行速度能决定你 CI 流程的好坏;</li>     <li>测试报告:P0,是否支持多种报告?是否方便与 CI 流程集成?比如要支持 XML、JUnit 等格式输出;</li>     <li>调试功能:P0,出错时提供的信息是否清晰?是否支持截图?TestCafe 做的很好;</li>     <li>测试组织:P1,是否能与现有的技术栈很好的组合起来?不能结合独创的方式是否直观?如果是大型项目可以提高优先级,Protractor 和 CodeCeptJs 占优;</li>     <li>代码风格:P1,简洁的代码意味的更高的可读性、更低的维护成本;TestCafe、CodeCeptJs 不错;</li>     <li>社区支持:P2,围绕这个工具是否有成熟的社区?可以用来提问、贡献代码;</li>     <li>可扩展性:P3,是否支持 API 扩展?扩展成本如何?</li>    </ul>    <p>如果你的项目中需要添加 E2E 测试,做决定的时候没有标准答案,因为还需要结合项目自身的特点,比如规模大小、对上面各因子的要求。</p>    <p>备注:本文的初始版本来源于 Medium 上的 <a href="/misc/goto?guid=4959746224202619573" rel="nofollow,noindex">文章</a> ,但是笔者在原文基础上重新设计了测试用例,每个测试框架的介绍也参与了笔者自身的使用经验,框架选型上也融入了自己的思考,有兴趣的可以去看原文。</p>    <p>  </p>   <p>来自:https://juejin.im/post/58dafcef0ce46300571bb918</p>    <p></p>    <p> </p>