javascript & qa测试工程师

122 阅读8分钟

专业名词注解

  • TDD: 测试驱动开发,关注所有的功能是否被实现(每一个功能都必须有对应的测试用例, suite配合test利用assert('tobi' == user.name)),国内企业能推动的比较少。
  • BDD: 行为驱动开发,关注整体行为是否符合整体预期,编写的每一行代码都有目的提供一个全面的测试用例集。expect/should, describe 配合 it 了利用自然语言 expect(1).toEqual(fn()) 去断言结果,国内企业多数采用的开发方式。

为什么需要 QA 测试

  • 正确性: 测试可以验证代码的正确性,在上线前做到心里有底。
  • 自动化: 当然手工也可以测试,通过console可以打印出内部信息,但是这是一次性的事情,下次测试还需要从头来过,效率不能得到保证。通过编写测试用例,可以做到一次编写,多次运行。
  • 解释性: 测试用例用于测试接口、模块,那么在测试用例中就会涉及如何使用这些 API。其他开发人员如果要使用这些 API,那阅读测试用例是一种很好地途径,有时比文档说明更清晰。
  • 驱动开发、指导设计: 代码被测试的前提是代码本身的可测试性,那么要保证代码的可测试性,就需要在开发中注意 API 的设计,TDD(测试驱动开发) 将测试前移就是起到这么一个作用。
  • 保证重构: 互联网行业产品迭代速度很快,迭代后必然存在代码重构的过程,那怎么才能保证重构后的代码质量呢 ? 有测试用例做后盾,就可以大胆的进行重构。

单元测试

  • 目的: 单元测试能够让开发者明确知道代码结果
  • 原则: 单一职责、接口抽象、层次分离
  • 断言库: 保证最小单元是否正常运行检测方法
  • 测试风格: 测试驱动开发(TDD)、行为驱动开发(BDD)均是敏捷开发方法论。

单元测试框架(star数统计截止到博客撰写时间)

  • better-assert (TDD断言库 Github 190star 19fork)
  • should.js (BDD断言库 Github 2295star 194fork)
  • expect.js (BDD断言库 Github 1391star 162fork)
  • chai.js (TDD BDD双模 Github 2823star 271fork)
  • Jasmine.js (BDD断言库 Github 10723star 1680fork)
  • Node.js (本身集成 require('assert'));
  • Intern (更是一个大而全的单元测试框架)
  • Qunit (一个游离在 jQuery 左右的测试框架)
  • Macaca (一套完整的自动化测试解决方案 国产神奇来自阿里巴巴)

单元测试运行流程

karma 集成 jasmine 驱动自动化单元测试

karma

  • npm install karma --save-dev
  • npm install karma-jasmine karma-phantomjs-launcher jasmine-core --save-dev
  • npm install karma-cli -g

附:目录结构

// unit/index.js 
window.add = function(a) { 
  return a + 1;
}

// unit/index.spec.js
describe('测试基本的函数API', function() {
  it ('+1函数的应用', function() {
    expect(window.add(1)).toBe(2);
  })
});
  • 根目录(unit-test)下执行 karma init 生成 karma配置文件

  • 编辑生成的 karma.conf.js

{
    files: [
      './tests/unit/*.js',
      './tests/unit/*.spec.js'
    ],
    browsers: ['Chrome'],  // 无头(虚拟)浏览器 二进制程序 无界面 只支持es5代码 可以持续集成
    singleRun: true, // 无头浏览器需设置为true 
}

// package.json 增加 scripts
"unit": "karma start"

// 执行测试
npm run unit

// 输出如下 代表测试通过
03 09 2020 21:48:51.027:WARN [filelist]: All files matched by "/Users/ys/2020-9月复习/unit-test/tests/unit/*.spec.js" were excluded or matched by prior matchers.
03 09 2020 21:48:51.046:INFO [karma-server]: Karma v5.2.1 server started at http://localhost:9876/
03 09 2020 21:48:51.046:INFO [launcher]: Launching browsers PhantomJS with concurrency unlimited
03 09 2020 21:48:51.051:INFO [launcher]: Starting browser PhantomJS
03 09 2020 21:48:52.194:INFO [PhantomJS 2.1.1 (Mac OS 0.0.0)]: Connected on socket cq53bpq4EaLPA5IRAAAA with id 4306079
PhantomJS 2.1.1 (Mac OS 0.0.0): Executed 1 of 1 SUCCESS (0.008 secs / 0.002 secs)
TOTAL: 1 SUCCESS


---------------------------------------------- 番外 ---------------------------------------------


// 如果要运行有头浏览器 比如 chrome 需要修改 karma.con.js
{
   browsers: ['Chrome'], // 有头的将来没法做持续集成
   singleRun: false // 设置为false时 当browsers不为PhantomJs(无头浏览器)时候 会打开浏览器
}

// 安装 karma-chrome-launcher 
- npm install karma-chrome-launcher --save-dev

// 执行测试 npm run unit
// 此时会先打开谷歌浏览器, 然后命令行输出执行结果, 然后关闭浏览器, 假设我们给定一个错误期望结果 expect(window.add(1)).toBe(3);
// 会输出如下
Chrome 85.0.4183.83 (Mac OS 10.13.6) 测试基本的函数API +1函数的应用 FAILED
        Error: Expected 2 to be 3.
            at <Jasmine>
            at UserContext.<anonymous> (tests/unit/index.spec.js:3:27)
            at <Jasmine>

玩点花样

---------------------  添加代码覆盖率检查优化 ---------------------
// 代码覆盖率检查 (覆盖总逻辑分支比例)
npm i karma-coverage --dev

// 我们把index.js 内容更改为
window.add = function(a) { 
  if (a == 1) {
    return 1
  } else {
      return a + 1;
  }
}

// index.spec.js 此处没有覆盖到 a !== 1的分支
describe("测试基本函数的API", function () {
    it("+1函数的应用", function () {
        expect(window.add(1)).toBe(1);
    })
});

// karma.config 增加
{
  preprocessors: {
  "./tests/unit/*.js" : ['coverage'] // 指定文件
},
  reporters: ['progress', 'coverage'], // 生成报表 coverage 覆盖率检查

  coverageReporter: { // 生成覆盖率报表的目录
    type: 'html',
    dir: './doc/coverage/' 
  }
}

执行 npm run unit ./docs/coverage 文件会生成一个覆盖率报表 打开 index.html

还可以点开index.js 查看没有覆盖到的分支行数奥

jest 对 React 组件做单测

Facebook出的 测试框架, 对 react 支持较好

  • 新建项目 react-jest, npm init -y 初始化
  • npm i @testing-library/react react-dom react-scripts jest-dom --save-dev
  • 添加文件 src/index.js src/index.spec.js
  • 附: 目录结构
  // src/index.js
  
  import React from 'react';

  export const App = () => {
    return (
      <div>
        <h2 data-testid="js-h2">(キ`゚Д゚´)!!</h2>
        <ul data-testid="js-ul">
          <li>Javascript</li>
          <li>CSS</li>
        </ul>
      </div>
    )
  }

  // src/index.spec.js

  import React from 'react';
  import  { render, cleanup, fireEvent } from '@testing-library/react';
  import { App } from './index';

  afterEach(cleanup); // 执行结果清除

  describe('基础的react单元测试', function() {
    it('index组件测试', function() {
      const { getByTestId } = render(<App />); // 渲染组件
      const [ul, nav] = [getByTestId('js-ul'), getByTestId('js-h2')]; // 找到元素

      expect(ul.children.length).toBe(2);
      expect(nav.textContent).toContain('(キ`゚Д゚´)!!');
    })
  });

  // package.json 添加命令 

  "scripts": {
    "jest": "react-scripts test --env=jsdom"
  }
  • 执行 npm run jest

e2e 测试

  • 即端对端测试,属于黑盒测试,通过编写测试用例,自动化模拟用户操作,确保组件间通信正常,程序流数据传递如预期。

使用 selenium-webdriver 实现 e2e 测试

  • 包链接
  • 新建项目 e2e, 初始化npm: npm init -y
  • npm install selenium-webdriver --save-dev
  • 项目根目录添加 baidu.spec.js
  • 附: 目录结构
// baidu.spec.js
const {Builder, By, Key, until} = require('selenium-webdriver');

(async function example() {
  let driver = await new Builder().forBrowser('chrome').build(); // 打开chrome浏览器
  try {
    await driver.get('http://www.baidu.com'); // 打开百度网页
    await driver.findElement(By.name('wd')).sendKeys('javascript', Key.RETURN); // 找到 name = wd 的输入框 输入 javascript 按下return键
    await driver.wait(until.titleIs('javascript_百度搜索'), 1000); //  下个页面标题应该等于 'javascript_百度搜索' 等待时间为1000毫秒
  } finally {
    await driver.quit();
  }
})();
  • 下载对应浏览器驱动 我们以谷歌为栗
  • 选择版本对应版本的驱动 (注意, 浏览器版本要和驱动版本对应)
  • 下载 解压到我们项目根目录
  • package.json中新增命令 e2e
  • 执行 npm run e2e 此时会自动打开我们的chrome浏览器 打开百度网页 输入 javascript 执行搜索
  • 驱动版本不符合浏览器版本报错(此时不能成功调起浏览器)
  • 执行成功的命令行打印
  • 执行失败的命令行打印

rize + puppeteer - 写法更清爽的e2e测试方案

rize: 一个可以让你简单、优雅的使用 puppeteer 的 Node.js 库 puppeteer: phantom.js(单例测试时候的无头浏览器)不维护后 这个是谷歌推出的 可以理解成我们日常使用的Chrome的无界面版本以及对其进行操控的js接口套装, 也是无头浏览器

  • npm install --save-dev puppeteer rize

附:目录结构

  // test/git.spec.js
  const Rize = require('rize');
  const rize = new Rize();

  rize
    .goto('https://github.com/') // 打开github
    .type('input.header-search-input', 'node') // 找到class为header-search-input的输入框 输入node
    .press('Enter') // 回车
    .waitForNavigation({ 'timeout': 0 }) // 等待跳转
    .assertSee('Node.js') // 断言是否页面出现 Node.js 文字
    .end() 

nightwatch(守夜人) - 更好用的e2e测试框架

这里不作讨论,有兴趣的话可以自己尝试下~

backstop.js 实现ui自动化测试

phantomCss 也能做,但是这里我们介绍功能更强大的backstopjs

  • 参考地址
  • 新建项目 ui-backstop, npm init -y
  • cnpm install -g backstopjs (略慢)
  • backstop init

附:目录结构

  // backstop.json
  {
    "id": "qq",
    "viewports": [
      {
        "label": "phone",
        "width": 375,
        "height": 667
      },
      {
        "label": "ipad",
        "width": 1024,
        "height": 768
      }
    ],
    "onBeforeScript": "puppet/onBefore.js", 
    "onReadyScript": "puppet/onReady.js",
    "scenarios": [
      {
        "label": "map",  // 以腾讯地图为例
        "cookiePath": "backstop_data/engine_scripts/cookies.json", // 如果网站需要登录 这里可以种植 cookie
        "url": "https://map.qq.com/m/",
        "referenceUrl": "",
        "readyEvent": "",
        "readySelector": "",
        "delay": 0,
        "hideSelectors": [],
        "removeSelectors": [],
        "hoverSelector": "",
        "clickSelector": "",
        "postInteractionWait": 0,
        "selectors": [],
        "selectorExpansion": true,
        "expect": 0,
        "misMatchThreshold" : 0.1,
        "requireSameDimensions": true
      }
    ],
    "paths": {
      "bitmaps_reference": "backstop_data/bitmaps_reference", // 参考的文件夹 ui图放里面
      "bitmaps_test": "backstop_data/bitmaps_test",
      "engine_scripts": "backstop_data/engine_scripts",
      "html_report": "./docs/backstop_data/html_report", // 导出报表路径
      "ci_report": "./docs/backstop_data/ci_report" // Jenkins 或者 travis 报表路径
    },
    "report": ["browser"],
    "engine": "puppeteer", // 包自带引擎 
    "engineOptions": {
      "args": ["--no-sandbox"]
    },
    "asyncCaptureLimit": 5,
    "asyncCompareLimit": 50,
    "debug": false,
    "debugWindow": false
  }

  • 执行 backstop test

mocha 实现异步代码测试(接口测试)

  • 新建项目 async-mocha, npm init -y
  • 新建目录 tests/service/router.spec.js
  • 根目录新建 mochaRunner.js
  • cnpm i axios --save-dev
  • cnpm i mocha --save
  • cnpm install --save-dev mochawesome // 报表

附:目录结构

  // router.spec.js
  const axios = require('axios');

  describe('node 接口', function() {
    it ('test 接口测试', function(done) {
      axios.get('http://slsljxjkss.com/sss')
      .then(function(res) {
        if (res.data.xx == '期望值') {
          done();
        } else {
          done(new Error('数据请求出错'));
        }
      }).catch(function(err) {
        done(err);
      });
    })
  });

  // mochaRunner.js
  const Mocha = require('mocha');

  const mocha = new Mocha({
    reporter: 'mochawesome', // 指定报表
    reporterOptions: {
      reportDir: './docs/mochawesome-reporter' 
    }
  });

  mocha.addFile('./tests/service/router.spec.js');
  mocha.run(function() {
    console.log('done');
    process.exit(); // 退出
  });
  • npm run mocha

  • 打开 docs 下生成的报表