VUE+jtest的组件单元测试

2,837 阅读6分钟

会议讨论结构

  • 代码测试用于基础组件库,类似于element
  • 业务层的测试可以对部分需要长期维护的代码进行测试

VUE的脚手架本身自带单元测试功能,可以选择karma和jtest两种,本文主要是测试了两种方式的不同。在讲到前端测试的时候我们需要区分以下两种测试概念:

  • e2e测试:测试一个应用从头到尾的流程是否和设计时候所想的一样。简而言之,它从一个用户的角度出发,认为整个系统都是一个黑箱,只有UI会暴露给用户。这种测试依赖于我们可以通过代理或者直接控制浏览器本身,来发起用户操作,来检验当前页面的链接,某个元素是否可用。 这个我选择了phantomJS和nightwatchJS来介绍,phantomJS本身是exe文件,不过通过nodejs包装提供了npm包,有npm插件,nightwatchJS是一个通过nodejs链接Selenium和chromedriver
  • unit测试:单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。

接下来通过几个demo看看具体的实现是怎么样子的。

phantomJS

这个是提供了针对各个浏览器的API

下载软件,安装环境变量

  • 截屏 获取dom元素 检测dom元素的属性
  • github.com/ariya/phant…
  • phantomjs rasterize.js https://www.xinhehui.com "a.png
  • 网络测速,网络访问,注入JS运行
  • phantomjs loadspeed.js https://www.xinhehui.com
var page = require('webpage').create();
page.open('http://example.com', function(status) {
  console.log("Status: " + status);
  if(status === "success") {
    page.render('example.png');
  }
  phantom.exit();
});

Nightwatch

Nightwatch是nodejs驱动的e2e测试框架,自带断言库,可以编写测试脚本

Node.js powered End-to-End testing framework

e2e测试框架,based on Node.js,Web应用E2E(End To End)测试是模拟用户进行页面操作,通过来判断页面状态的变化,从而检查功能是否运行正常的测试方法

  • 并行处理
  • 使用W3C WebDriver API ,之前基于Selenium API
  • 基于Chai断言库
  • 部署有点繁琐,需要下载selenium-server-standalone-3.8.1.jar,因为它基于selenium的,chromedriver.exe,这个是根据自己需要测试用的浏览器来选择,然后下载对应的浏览器模块
  • 弄好了之后,就是挨个启动,弄个配置
require('babel-register')
var config = require('../../config')

// http://nightwatchjs.org/gettingstarted#settings-file
module.exports = {
  src_folders: ['test/e2e/specs'],
  output_folder: 'test/e2e/reports',
  custom_assertions_path: ['test/e2e/custom-assertions'],

  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
      }
    }
  }
}

业务代码,

// For authoring Nightwatch tests, see
// http://nightwatchjs.org/guide#usage
//const myAssertion = require('./my_assertion')
//require('nightwatch/bin/runner.js');

module.exports = {
  '测试投资页面有数据': function (browser) {
    // automatically uses dev Server port from /config.index.js
    // default: http://localhost:8080
    // see nightwatch.conf.js
    //const devServer = 'http://local.xinhehui.com:9080/#/'
    const devServer = 'http://wap-test4.xinhehui.com'
    browser
      .url(devServer)
      .waitForElementVisible('#wrap', 5000);
      // 跳转到投资页面
    browser.waitForElementVisible('.footerbox', 5000);
    browser.assert.visible(".footerbox li:nth-child(2) p");

    browser.execute(function() {
      document.querySelector('.footerbox li:nth-child(2) p').click()
      return true;
        
    },[],function(res){
      browser.waitForElementVisible('.productList', 5000);
      // 测试投资
      browser.assert.elementPresent('.productList>ul>li')
    })


    
  }
}

这是e2e测试,相当于测试的工作了,可以直接自动化测试需求

unit单元测试

  • TDD(测试驱动开发)
  • BDD(行为驱动开发)

单元测试分为 TDD(测试驱动开发)和 BDD(行为驱动开发)两种类型 Jtest Mocha(发音"摩卡")诞生于2011年,是现在最流行的JavaScript测试框架之一,在浏览器和Node环境都可以使用

// TDD
suite('Array', function() {
  setup(function() {
  });

  test('equal -1 when index beyond array length', function() {
    assert.equal(-1, [1,2,3].indexOf(4));
  });
});

// BDD
describe('Array', function() {
  before(function() {
  });

  it('should return -1 when no such index', function() {
    [1,2,3].indexOf(4).should.equal(-1);
  });
});

这两种不同的编码方式,这只是断言库的选择,像Jtest会针对这两种都有实现

karma

var alias = require('../../build/alias')
const vueLoaderConfig = require('../../build/vue-loader.conf')
const utils = require('../../build/utils')
var webpack = require('webpack')

var webpackConfig = {
  resolve: {
    alias: alias
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.less$/,
        use: [{
            loader: "style-loader" // creates style nodes from JS strings
        }, {
            loader: "css-loader" // translates CSS into CommonJS
        }, {
            loader: "sass-loader" // compiles Less to CSS
        }]
      },
      {
        test: /\.scss$/,
        use: [{
            loader: "style-loader" // creates style nodes from JS strings
        }, {
            loader: "css-loader" // translates CSS into CommonJS
        }, {
            loader: "sass-loader" // compiles Sass to CSS
        }]
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      __WEEX__: false,
      'process.env': {
        NODE_ENV: '"development"',
        TRANSITION_DURATION: process.env.CI ? 100 : 50,
        TRANSITION_BUFFER: 10
      }
    })
  ],
  devtool: '#inline-source-map'
}

// shared config for all unit tests
module.exports = {
  frameworks: ['jasmine'],
  files: [
    './index.js'
  ],
  preprocessors: {
    './index.js': ['webpack', 'sourcemap']
  },
  webpack: webpackConfig,
  webpackMiddleware: {
    noInfo: true
  },
  plugins: [
    'karma-jasmine',
    'karma-mocha-reporter',
    'karma-sourcemap-loader',
    'karma-webpack'
  ]
}

karma是一个独立的,可以自由加载各种插件,比较有意思的可以通过配置进行实际设备的测试,如果要进行无浏览器的测试需要可以使用puppeteer 安装的东西还包括chrome-win32.exe 放置在文件夹中,指定目录

jest Javascript单元测试工具-Jest

DOM结构测试

  • Fast and sandboxed
  • Built-in code coverage reports
  • Zero configuration (用react的话)
  • babel-jest is automatically installed when installing Jest and will automatically transform files if a babel configuration exists in your project
  • Snapshot 快照对比UI 实际是html代码对比
  • https://alexjoverm.github.io/series/Unit-Testing-Vue-js-Components-with-the-Official-Vue-Testing-Tools-and-Jest/

Puppeteer 因为karma需要需要使用浏览器组件来跑,现在的业务需要知识单纯进行单元测试,所以选择使用jest,现在karam的配置是jasmine,jtest的配置默认也是jasmine,所以不管后续是不是切换到karma,都可以直接不改测试代码

http://csbun.github.io/blog/2017/09/puppeteer/

jtest 介绍

组件测试

https://github.com/sitepoint-editors/testing-react-with-jest/

  • 功能测试; 模拟事件,比如点击,来判定传的参数是否正确;
  • 状态测试;组件基于数据流,通过调用暴露的方法作用于初始props来比较state的变化;
  • 快照测试;DOM不变的情况下,同样的输入会得到同样的html片段; jtest 和 karma区别
  • 相同点,对于具体的测试代码无差别
  • jtest 内置jsdom,jasmine2,快照测试,默认不需要webpack,直接对CSS,图片,字体等资源直接过滤处理,而karma需要安装相关的插件,需要使用webpack编译文件

实际情况 复杂的依赖 解决 加入moduleNameMapper,为什么没用webpack,官方推荐不用 请求

  • Object.keys 报错 alias的变量名加上更具体的标识符,引用的组件share和corejs组件里的require(./share)冲突,
  • localstorage不存在 原因 jsdom环境中不存在 解决 先给jsdom环境加上变量 再延迟加载初始化库,但是由于nodejs不方便加载es6模块,最好是重写一个文件或者利用babel编译,所以使用了setupTestFrameworkScriptFile 来处理,这个会有先后顺序
  • 最后的最后
import Vue from 'vue'

process.env.NODE_ENV = 'development'

/*** 给当前的jsom 执行环境 widnow monkeypack ***/
window.localStorage = window.sessionStorage = {
  getItem: function (key) {
      return this[key]
  },
  setItem: function (key, value) {
      this[key] = value
  }
}

var _pluginCtrl = require('@/controllers/pluginCtrl');

var _pluginCtrl2 = _interopRequireDefault(_pluginCtrl);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

Vue.config.productionTip = false
_pluginCtrl2.default.init(Vue)

jest api

一些API,比较有趣,在这里做下介绍 https://facebook.github.io/jest/docs/en/timer-mocks.html

  • 通过mock直接替换掉require进来的模块
  • https://github.com/facebook/jest/blob/master/examples/snapshot/tests/clock.react.test.js 直接替换掉执行代码中某些变量

使用jest.fn来包装是可以调用toHaveBeenCalled参数