前端测试初探

一直知道前端也是有测试的,但理解很肤浅,今天下定决心摸索一遍到底什么是前端自动化测试...
本例子通过vue-cli生成的unite2e来探讨...

基础名词

一些前端测试的名词解释:

  • karma是一个基于Node.jsJavaScript测试执行过程管理工具,其在测试中的作用相当于开发构建中使用的webpack

  • karma-webpack连接karmawebpack的桥梁。不经过webpack编译命令的文件是无法独立运行的,karma需要了解你的webpack配置,决定如何处理你的测试文件。

  • karma-phantomjs-launcherphantomjskarma中的启动器,由此引出了PhantomJS,一个基于webkit内核的无头浏览器,即没有UI界面,即它就是一个浏览器,只是其内的点击、翻页等人为相关操作需要程序设计实现。在查找相关资料时,也发现了其他的常规浏览器launcher,比如:Chrome、Firefox、Safari、IE 等,以应对不同浏览器或多浏览器的测试需求。见Browsers

  • karma-sourcemap-loader一个Karma插件,生成文件的sourcemap

  • karma-mocha让你在karma中使用Mocha一款功能丰富的javascript单元测试框架,它既可以运行在nodejs环境中,也可以运行在浏览器环境中

  • karma-sinon-chai让你在karma中使用sinon-chai断言库的插件, 提供丰富的断言方法,前置依赖有sinon-chaisinonchai

  • karma-spec-reporter用于将测试结果显示到控制台。

  • karma-coverage用来生成代码覆盖率。

  • Nightwatch是一套基于Node.js的测试框架,使用Selenium WebDriver API以将Web应用测试自动化。它提供了简单的语法,支持使用JavaScript和CSS选择器,来编写运行在Selenium服务器上的端到端测试。

相关配置

unit目录结构,主要测试单元是一个个函数、方法

└── unit
    ├── coverage  代码覆盖率报告,src下面的index.html可以直接用浏览器打开
    │   ├── lcov-report
    │   │   ├── base.css
    │   │   ├── index.html
    │   │   ├── prettify.css
    │   │   ├── prettify.js
    │   │   ├── sort-arrow-sprite.png
    │   │   ├── sorter.js
    │   │   └── src
    │   │       ├── App.vue.html
    │   │       ├── components
    │   │       │   ├── Hello.vue.html
    │   │       │   └── index.html
    │   │       └── index.html
    │   └── lcov.info
    ├── index.js 运行测试用例前先加载的文件,方便统计代码覆盖率
    ├── karma.conf.js karma的配置文件
    └── specs 所有的测试用例都放在这里
        └── Hello.spec.js

karma.conf.js内容

module.exports = function (config) {
    config.set({
        // 要启动的测试浏览器
        browsers: [ 'Chrome'],
        // 测试框架
        frameworks: ['mocha', 'sinon-chai'],
        // 测试报告处理
        reporters: ['spec', 'coverage'],
        // 要测试的目标文件
        files: ['./index.js'],
        // 忽略的文件
        exclude: [],
        // 预处理文件
        preprocessors: {
            './index.js': ['webpack', 'sourcemap']
        },
        // webpack
        webpack: webpackConfig,
        webpackMiddleware: {
            noInfo: true
        },
        // Coverage options
        coverageReporter: {
            dir: './coverage',
            reporters: [
                { type: 'lcov', subdir: '.' },
                { type: 'text-summary' }
            ]
        },
        // true: 自动运行测试并退出
        // false: 监控文件持续测试
        singleRun: true,
        // 以下是 vue-cli 没有生成的一些配置
        // 文件匹配的起始路径
        // basePath: '',
        // 服务器端口
        // port: 9876,
        // 输出着色
        // colors: true,
        // 日志级别
        // LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
        // logLevel: config.LOG_INFO,
        // 监控文件更改
        // autoWatch: true,
        // 超时处理,6s内没有捕获浏览器将终止进程
        // captureTimeout: 6000
    })
}

index.js入口文件

// 加载所有的测试用例、 testsContext.keys().forEach(testsContext)这种写法是webpack中的加载目录下所有文件的写法

// 匹配的是specs目录,里面是存放的是测试用例
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)

// 加载所有代码文件,方便统计代码覆盖率
// 匹配的是src目录,除了main.js以外的所有文件。
const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
srcContext.keys().forEach(srcContext)

e2e目录结构,测试的单元是一个个预期的行为表现,打开游览器,模拟测试

├── e2e
│   ├── custom-assertions
│   │   └── elementCount.js 自定义的断言方法
│   ├── nightwatch.conf.js nightwatch的配置文件
│   ├── reports 
│   │   ├── CHROME_60.0.3112.101_Mac\ OS\ X_test.xml
│   │   └── CHROME_60.0.3112.113_Mac\ OS\ X_test.xml
│   ├── runner.js  bootstrap文件,起我们的页面server和nightwatch文件
│   └── specs
│       └── test.js 测试用例

nightwatch.conf.js内容

  src_folders: ['test/e2e/specs'],
  output_folder: 'test/e2e/reports',
  custom_assertions_path: ['test/e2e/custom-assertions'],
    // 对selenium的配置
  selenium: {
    start_process: true,
    server_path: require('selenium-server').path,
    host: '127.0.0.1',
    port: 4444,
    cli_args: {
      'webdriver.chrome.driver': require('chromedriver').path
    }
  },
    // 测试环境的配置
  test_settings: {
    default: {
      selenium_port: 4444,
      selenium_host: 'localhost',
      silent: true,
      globals: {
        devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
      }
    },

    chrome: {
      desiredCapabilities: {
        browserName: 'chrome',
        javascriptEnabled: true,
        acceptSslCerts: true
      }
    },

    firefox: {
      desiredCapabilities: {
        browserName: 'firefox',
        javascriptEnabled: true,
        acceptSslCerts: true
      }
    }
  }

runner.js入口文件内容,先起一个我们的网页服务然后再起nightWatch服务

var server = require('../../build/dev-server.js')

server.ready.then(() => {
  // 2. run the nightwatch test suite against it
  // to run in additional browsers:
  //    1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
  //    2. add it to the --env flag below
  // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
  // For more information on Nightwatch's config file, see
  // http://nightwatchjs.org/guide#settings-file
  var opts = process.argv.slice(2)
    console.log(opts);
  if (opts.indexOf('--config') === -1) {
    opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
  }
  if (opts.indexOf('--env') === -1) {
    opts = opts.concat(['--env', 'chrome,firefox'])
  }

  var spawn = require('cross-spawn')
  var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })

  runner.on('exit', function (code) {
    server.close()
    process.exit(code)
  })

  runner.on('error', function (err) {
    server.close()
    throw err
  })
})

工具详解

chai

定义几个函数

const math = {
    add: (...args) => args.reduce((num, value) => num + value),
    mul: (...args) => args.reduce((num, value) => num * value),
    cover: (a, b) => {
        if (a > b) {
            return a - b
        } else if (a == b) {
            return a + b
        } else {
            return -1
        }
    }
}

node自带的断言测试

const assert = require('assert')
const {add, mul} = require('./math')
assert.equal(add(2, 3), 5)

引入chai库测试,3个方法作用一样,断言风格不同而已

const chai = require('chai')
// should
chai.should()
add(2, 3).should.equal(5)
// expect
consr expect = chai.expect
expect(add(2, 3)).to.be(5)
// assert
consr assert = chai.assert
assert.equal(add(2, 3), 5)

expect断言的优点是很接近自然语言,下面是一些例子

// 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect(foo).to.be.deep.equal({ bar: 'baz' });

// 布尔值为true
expect('everthing').to.be.ok;
expect(false).to.not.be.ok;

// typeof
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);

// include
expect([1,2,3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');

// empty
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;

// match
expect('foobar').to.match(/^foo/);

基本上,expect断言的写法都是一样的。头部是expect方法,尾部是断言方法,比如equal、a/an、ok、match等。两者之间使用to或to.be连接

如果expect断言不成立,就会抛出一个错误。只要不抛出错误,测试用例就算通过

Mocha

Mocha的作用是运行测试脚本,首先必须学会写测试脚本。所谓"测试脚本",就是用来测试源码的脚本
Mocha默认运行test子目录里面的测试脚本 添加--recursive参数可以运行test目录下所有层数用例
基本用法:

describe('#math', () => {
    describe('add', () => {
        it('should return 5 when 2 + 3', () => {
            assert(add(2, 3), 5)
        })
    })
    describe('mul', () => {
        it('should return 6 when 2 * 3', () => {
            assert(mul(2, 3), 6)
        })
        // 只执行此条
       it.only('should return 6 when 2 * 3', () => {
            assert(mul(2, 3), 6)
        })
       // 忽略此条
       it.skip('should return 6 when 2 * 3', () => {
            assert(mul(2, 3), 6)
        })
    })
    describe('mul', () => {
        it('should return -1 when 2 < 3', () => {
            assert(cover(2, 3), -1)
        })
        it('should return 1 when 3 > 2', () => {
            assert(cover(3, 2), 1)
        })
        it('should return 4 when 2 = 2', () => {
            assert(cover(2, 2), 4)
        })
    })
})

异步例子:
it块执行的时候,传入一个done参数,当测试结束的时候,必须显式调用这个函数,告诉Mocha测试结束了
需要用-t--timeout参数,改变默认的(2000)超时设置。

// $ mocha -t 5000 timeout.test.js

it('测试应该5000毫秒后结束', done => {
  var x = true;
  var f = function() {
    x = false;
    expect(x).to.be.not.ok;
    done(); // 通知Mocha测试结束
  };
  setTimeout(f, 4000);
});

测试用例的钩子:
Mochadescribe块之中,提供测试用例的四个钩子:before()、after()、beforeEach()和afterEach()

describe('hooks', function() {

  before(function() {
    // 在本区块的所有测试用例之前执行
  });

  after(function() {
    // 在本区块的所有测试用例之后执行
  });

  beforeEach(function() {
    // 在本区块的每个测试用例之前执行
  });

  afterEach(function() {
    // 在本区块的每个测试用例之后执行
  });

  // test cases
});

benchmark

benchmark是一个测试函数性能的库

var suite = new Benchmark.Suite;
// add tests
suite.add('RegExp#test', function() {
  /o/.test('Hello World!');
})
.add('String#indexOf', function() {
  'Hello World!'.indexOf('o') > -1;
})
.add('String#match', function() {
  !!'Hello World!'.match(/o/);
})
// add listeners 
.on('cycle', function(event) {
  console.log(String(event.target));
})
.on('complete', function() {
  console.log('Fastest is ' + this.filter('fastest').map('name'));
})
// run async
.run({ 'async': true });

Puppeteer

Puppeteer是类似于Nightwatch的一个Chrome专用版,有更友好的api,是用来测试游览器环境的一个工具
也可用于爬虫,比如这个demo演示了爬取百度图片,相较于cheerio,它的爬虫更模拟真实环境,不易反爬虫

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({path: 'example.png'});

  await browser.close();
})();

Jest

哦,是不是这一大堆东西看晕了,用个karma还要集成一大堆各种插件配置,这一点上真是跟webpack一样了
就像有人受不了webpack这一大堆配置所以有了前端构建集成工具Parcel
Jest就是这样一个前端测试集成工具
Jest的官方文档支持中文,这里就不详细说明了,有兴趣可以去官网查看
相比于karma最大特点就是快和方便,缺点就是没有karma测试环境真实和自由
具体抉择,仁者见仁智者见智啦~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容