# JavascriptQA 工程师

工程师:白盒 黑盒

# 测试核心概念

  • 单元测试
  • 性能测试
  • 安全测试
  • 功能测试
  • UI 还原测试

# 为什么要测试

  • 正确性 测试可以验证代码的正确性,在上线前做到心里有底
  • 自动化 通过 console 打印内部消息的手工测试是一次性的事情,下次测试还得从头再来,效率不能得到保证。通过编写测试用例,一次编写,多次运行
  • 解释性 测试用例用于测试接口、模块的重要性。在测试用例中涉及使用到的 Api 的测试用例有时会比文档更清晰
  • 驱动开发,指导设计 代码被测试的前提是代码本身的可测试性,那么要保证代码的可测试性,就需要在开发中注意 Api 的设计,TDD(测试驱动开发)
  • 保证重构 产品迭代速度快,有测试用例做后盾,可以大胆的进行重构

# 单元测试

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

TDD 关注所有功能是否被实现(每一个功能都必须有对应的测试用例),suite配合test利用assert("tobi"==user.name); BDD 关注整体行为是否符合整体预期,编写的每一行代码都有目的的提供一个全面的测试用例集。expect/should, describe 配合 it 利用自然语言处理expect(1).toEqual(fn())执行结果。

# 单元测试框架

  • better-assert(TDD 断言库)
  • should.js(BDD 断言库)
  • expect.js(BDD 断言库)
  • chai.js(TDD BDD 双模) // 比较常用的
  • Jasmine.js(BDD) // 比较常用的
  • Node.js 本身集成 require("assert");
  • Intern 更大而全的单元测试框架
  • QUnit 一个游离在 JQuery 左右的测试框架
  • Macaca 国产(阿里)

# 单元测试运行流程

每个测试用例组通过 describe 进行设置 before->beforeEach->it->after->afterEach

  • 1,before 单个测试用例(it)开始前
  • 2,beforeEach 每个测试用例开始前
  • 3,it: 定义测试用例,并利用断言库进行设置 chai。如:expect(x).to.equal(true);异步 mocha。
  • 4,以上专业术语叫 mock

# 自动化单元测试

karma 自动化 runner 集成
PhantomJS 无刷新 Phantomcss ui 测试

$ npm install karma --save-dev
# Install plugins that your project needs:
$ npm install karma-jasmine karma-chrome-launcher jasmine-core --save-dev

$ npm i karma-coverage --save-dev # 代码覆盖率


# npm install -g karma-cli
./node_modules/karma/bin/karma init


"jasmine-core": "^3.7.1",
"karma": "^6.3.2",
"karma-chrome-launcher": "^3.1.0",
"karma-jasmine": "^4.0.1",
"karma-phantomjs-launcher": "^1.0.4"
npm install karma --save-dev
npm install karma-cli --save-dev
npm install karma-chrome-launcher --save-dev
npm install karma-phantomjs-launcher --save-dev
npm install karma-mocha --save-dev
npm install karma-chai --save-dev
npm install karma-coverage --save-dev【报告和测试覆盖率检查】
[coverageReporter: {type:"html", dir:"coverage/"}]
"karma": "^1.4.1",
"chai": "^4.1.2",
"cross-env": "^5.0.1",
"karma-coverage": "^1.1.1",
"karma-mocha": "^1.3.0",
"karma-phantomjs-launcher": "^1.0.2",
"karma-phantomjs-shim": "^1.4.0",
"karma-sinon-chai": "^1.3.1",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.31",
"karma-webpack": "^2.0.2",
"mocha": "^3.2.0",
"sinon": "^4.0.0",
"sinon-chai": "^2.8.0",
"inject-loader": "^3.0.0",
"babel-plugin-istanbul": "^4.1.1",
"phantomjs-prebuilt": "^2.1.14",

# 基准测试

  • 面向切面编程 AOP 无侵入式
  • Benchmark 基准测试方法,它并不是简单地统计执行多少次测试代码后对比时间,它对测试有着严密的抽样过程。执行多少次取决于采样到数据能否完成统计。根据统计次数计算方差。

# 压力测试

  • 对网络接口做压力测试需要检查的几个常用指标有:吞吐率、响应时间、并发数,这些指标反映了服务器并发处理能力。
  • PV 网站当日访问人数 UV 独立访问人数。 PV 每天在几十万过更高就需要考虑压力测试。换算公式:QPS=PV/t。【1000000/106060=27.7 =》 100 万请求集中在 10 个小时,服务器每秒处理 27.7 个业务请求】
  • 常用压力测试工具是 ab、siege、http_load
  • ab -c 100 -n 100 http://localhost:8001 每秒持续发出 28 个请求。 Request per second 表示服务器每秒请求数 即为 QPS Failed requests 表示此次请求失败的请求数 理论上压测值越大,增加 ConnectionTimes 连接时间,它包括客户端向服务器端建立连接、服务器端处理请求、等待报文响应等过程。

# 安全测试

安全漏洞检查

  • XSS
  • SQL
  • CSRF

# 功能测试

# 用户真实性检查

selenium-webdriver
protractor selenium-standalone
http://webdriver.io webdriver i/o

# 冒烟测试 SmokeTest

自由测试的一种,找到 BUG 开发修复,然后专门针对此 BUG,优点:节省时间,防止 build 失败,缺点是覆盖率低。

# 回归测试

修改一处对整体功能全部测试,一般配合自动化测试。

# Javascript Lint & Hint

  • 目的:检测 Javascript 代码标准
  • 原因:Javascript 代码诡异,保证团队代码规范
  • 搭配自动化任务管理工具完善自动化测试 grunt-jshint、grunt-jslint

# 具体实践

短点 1,karma 集成 webpack 2,selenium-webdriver 的具体实现

# karma 单元测试

1,安装 karma 环境

// 无头浏览器
npm install phantomjs --save-dev
// karma发射器
npm install --save-dev karma-phantomjs-launcher
npm install karma --save-dev
npm install karma-jasmine karma-chrome-launcher jasmine-core --save-dev
// karma-chrome-launcher 打开chrome浏览器
// jasmine-core 断言库

2,karma init Which testing framework do you want to use ? 使用什么断言库

  • jasmine Do you want to use Require.js ?
  • no Do you want to capture any browsers automatically ? 自动化运行环境【测试运行环境放哪】
  • PhantomJS // 无头浏览器 What is the location of your source and test files ? 哪些文件需要监听 "test/**/*Spec.js". Should any of the files included by the previous patterns be excluded ? 哪些文件不管

Do you want Karma to watch all the files and run the tests on change ? 是否监测测试文件的变化

  • no

就会生成一个 karma.conf.js 到根目录

3,测试 xx.spec.js karma start

4,生成报告 npm install karma-coverage --save-dev

module.exports = function(config){
  config.set({
    files:[
      "src/**/*.js",
      "test/**/*.js",
    ],
    reporters: ["progress", "coverage"],
    // 指定对应的JS文件,去执行代码覆盖率
    preprocessors: [
      "src/**/*.js": ["coverage"]
    ],
    // 生成报表
    coverageReporter: {
      type: "html",
      dir: "coverage/"
    }
  })
}

如果想 karma start 运行,karma-coverage 必须全局装

PM 产品 FE 前端 RD 后端

# backstop UI 走查

  • 用 Puppeteer 脚本模拟用户交互
  • CLI 报告
  • JUnit 报告
  • 与 CI 和源代码管理配合使用 1,npm install -g backstopjs 2,backstop init
    根目录会有 backstop.json 和 engine_scripts 文件夹 backstop.json
/// backstop.json
id: "自动生成一个文件,避免与BackstopJS资源名冲突",
// 测试项目的一系列屏幕尺寸对象(至少一个)
scenarios: [
  {
    "label": "phone",
    "width": 320,
    "height": 480
  }
],
// 分块截图
scenarios: [
  {
    label: "截图名称",
    "url": "项目地址"
  }
]
// 输出配置
paths

engine_scripts 里可以设置 cookies 等,可以编程。

# ui 图放在 bitmaps_reference 里

backstop_default_BackstopJS_Homepage_0_document_0_web.png

3, backstop test

# e2e 测试 自动化 selenium-webdriver

1,

// npm install selenium-webdriver
// 下载驱动
const { Builder, By, Key, until } = require("selenium-webdriver");
(async function example() {
  // 打开启动
  let driver = await new Builder().forBrowser("firefox").build();
  try {
    await driver.get("http://www.google.com/ncr");
    await driver.findElement(By.name("q")).sendKeys("webdriver", Key.RETURN);
    await driver.wait(until.titleIs("webdriver - Google Search"), 1000);
  } finally {
    await driver.quit();
  }
})();

2, 下载驱动,并解压到本地 npm 页面有好多浏览器的下载包,选择适合本机的下载 3, npm install --save-dev puppeteer rize

// 为了看到这个过程发生了什么headless: false
const Rize = require("rize");
const rize = new Rize({ headless: false });
rize
  .goto("https://github.com/")
  .type("input.header-search-input", "node")
  .press("Enter")
  .waitForNavigation()
  .saveScreenshot("searching-node.png")
  .end();

# service 异步测试

// service/app.js
const express = require("express");
const app = express();
app.get("/test", (req, res) => {
  res.send({
    data: "222",
  });
});
const server = app.listen(3000, () => {
  console.log("server start at 3000");
});
module.exports = app;

# mocha service 异步测试

npm i mocha --save-dev
# mochawesome 测试报表
mocha test.js --reporter mochawesome --reporter-options reportDir=customReportDir,reportFilename=customReportFilename

1,新建 mochaRunner.js 文件

npm i mocha --save
npm i mochawesome --save

const Mocha = require("mocha");
const mocha = new Mocha({
  reporter: "mochawesome",
  reporterOptions: {
    reportDir: "./docs/mochawesome-report",
    reportFilename: "mochawesome-report",
    quiet: true,
  }
});

mocha.addFile("./service/router.spec.js");
mocha.run(function () {
  process.exit();
})
npm install supertest --save
// router.spec.js
const superagent = require("supertest");

const app = require("./app");

function request() {
  return superagent(app.listen());
}

describe("后台接口测试", function() {
  it("test接口测试", (done) => {
    request()
      .get("/test")
      .expect("Content-Type", /json/)
      .expect(200)
      .end(function(err, response) {
        console.log(response);
        if (response.data == "ok message") {
          done("ok");
        } else {
          done("err");
        }
      });
  });
});

# chai 断言库

npm i chai --save
const axios = require("axios");
const { expect } = require("chai");

describe("后台接口测试", function() {
  it("test接口测试", function(done) {
    axios
      .get("http://localhost:3100/test")
      .then(function(response) {
        console.log(response.data.data);
        expect(response.status).to.equal(200);
        if (response.data.data == "ok message111") {
          done();
        } else {
          done(new Error("数据不符合预期"));
        }
      })
      .catch(function(error) {
        done(error);
      });
  });
});

# chai 断言库常用语句

chai.should();
foo.should.be.a("string");
foo.should.equal("bar");
foo.should.have.lengthOf(3);
tea.should.have.property("flavors").with.lengthOf(3);
var expect = chai.expect;
expect(foo).to.be.a("string");
expect(foo).to.equal("bar");
expect(foo).to.have.lengthOf(3);
expect(tea)
  .to.have.property("flavors")
  .with.lengthOf(3);
var assert = chai.assert;
assert.typeOf(foo, "string");
assert.equal(foo, "bar");
assert.lengthOf(foo, 3);
assert.property(tea, "flavors");
assert.lengthOf(tea.flavors, 3);

# jest 测试

// shell
npm i jest --save-dev
// package.json
"e2e": "jest test/"

// test/index.test.js
const { Builder, By, Key, until } = require("selenium-webdriver");
describe("test google.com", () => {
  var driver;
  beforeEach(() => {
    console.log(111);
    driver = new Builder().forBrowser("firefox").build();
  });

  afterEach(() => {
    console.log(222);
    driver.quit();
  });

  it("should open google search", async () => {
    await driver.get("http://www.google.com/ncr");
    await driver.findElement(By.name("q")).sendKeys("webdriver", Key.RETURN);
    await driver.wait(until.titleIs("webdriver - Google 搜索"), 1000);
    await driver.getTitle().then((title) => {
      console.log("```title: " + title);
      expect(title).toEqual("webdriver - Google 搜索");
    });
  }, 300000);
});