ホームページ  >  記事  >  ウェブフロントエンド  >  Vue単体テストにおけるKarma+Mochaの詳細説明

Vue単体テストにおけるKarma+Mochaの詳細説明

亚连
亚连オリジナル
2018-06-08 16:56:331919ブラウズ

この記事では主に Vue のユニットテストの詳細な説明を紹介します。Karma+Mocha の学習メモを共有して参考にします。

vue-cli を使用してプロジェクトを作成する場合、単体テストと e2e テストをインストールするかどうかを尋ねられます。これら 2 つのテスト フレームワークの使用が公式に推奨されているので、学習して実践してみましょう。

はじめに

Karma

Karma は、Node.js をベースにした JavaScript テスト実行プロセス管理ツール (Test Runner) です。 Vue におけるこのツールの主な機能は、テストのためにさまざまな主流の Web ブラウザーでプロジェクトを実行することです。

言い換えれば、ブラウザ環境でコードをテストできるようにするテストツールです。これが必要な理由は、コードがブラウザー側で実行されるように設計されている可能性があり、ノード環境でのテストでは一部のバグが検出されない可能性があるためです。また、ブラウザーに互換性の問題がある場合、karma は次の手段を提供します。複数のブラウザ上でコードを自動的に実行します。ブラウザ (Chrome、Firefox、IE など) 環境で実行します。コードがノード側でのみ実行される場合は、karma を使用する必要はありません。

Mocha

Mocha は、chai アサーション ライブラリを使用して vue-cli で単体テストを実装するテスト フレームワークです。

Chai アサーション ライブラリについては、Chai.js アサーション ライブラリ API の中国語ドキュメントを参照してください。それを確認して使用するだけですぐに習得できます。

テストフレームワークについての私の理解

npm実行ユニット実行プロセス

  1. npm実行ユニットコマンドを実行します

  2. Karma実行環境を開きます

  3. Mochaを使用して書かれたものを1つずつテストしますChai アサーションを使用したテスト ケース

  4. は、ターミナルにテスト結果を表示します

  5. テストが成功すると、karma-coverage はテスト カバレッジ結果を含む Web ページを ./test/unit/coverage フォルダーに生成します。

Karma

Karma については、その構成オプションについて学びました。

以下は簡単なコメント付きの Vue の Karma 構成です:

var webpackConfig = require('../../build/webpack.test.conf')

module.exports = function (config) {
 config.set({
  // 浏览器
  browsers: ['PhantomJS'],
  // 测试框架
  frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
  // 测试报告
  reporters: ['spec', 'coverage'],
  // 测试入口文件
  files: ['./index.js'],
  // 预处理器 karma-webpack
  preprocessors: {
   './index.js': ['webpack', 'sourcemap']
  },
  // Webpack配置
  webpack: webpackConfig,
  // Webpack中间件
  webpackMiddleware: {
   noInfo: true
  },
  // 测试覆盖率报告
  // https://github.com/karma-runner/karma-coverage/blob/master/docs/configuration.md
  coverageReporter: {
   dir: './coverage',
   reporters: [
    { type: 'lcov', subdir: '.' },
    { type: 'text-summary' }
   ]
  }
 })
}

Mocha と Chai

公式の例を見てみましょう (すべてコードの意味を説明するコメント付き):

import Vue from 'vue' // 导入Vue用于生成Vue实例
import Hello from '@/components/Hello' // 导入组件
// 测试脚本里面应该包括一个或多个describe块,称为测试套件(test suite)
describe('Hello.vue', () => {
 // 每个describe块应该包括一个或多个it块,称为测试用例(test case)
 it('should render correct contents', () => {
  const Constructor = Vue.extend(Hello) // 获得Hello组件实例
  const vm = new Constructor().$mount() // 将组件挂在到DOM上
  //断言:DOM中class为hello的元素中的h1元素的文本内容为Welcome to Your Vue.js App
  expect(vm.$el.querySelector('.hello h1').textContent)
   .to.equal('Welcome to Your Vue.js App') 
 })
})

知識知っておくべき点:

  1. テスト スクリプトは test/unit/specs/ ディレクトリに配置する必要があります。

  2. スクリプトの命名方法は[コンポーネント名].spec.jsです。

  3. いわゆるアサーションは、コンポーネントに対していくつかの操作を実行し、結果を予測することです。テスト結果がアサーションと同じであれば、テストは合格します。

  4. デフォルトでは、単体テストは main.js を除く src ディレクトリ内のすべてのファイルをテストします。main.js は test/unit/index.js ファイルで変更できます。

  5. Chai アサーション ライブラリでは、to be は、that と have with at を同じものとします。これらの言語チェーンは意味がなく、理解を容易にするためのものです。

  6. テストスクリプトは複数の descibe で構成され、それぞれの description は複数の it で構成されます。

  7. 非同期テストを理解する

it('异步请求应该返回一个对象', done => {
  request
  .get('https://api.github.com')
  .end(function(err, res){
   expect(res).to.be.an('object');
   done();
  });
});

describeのフック(ライフサイクル)を理解する

describe('hooks', function() {

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

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

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

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

 // test cases
});

実践

上記は単体テストの使い方を簡単に紹介したので、Vueで単体テストを始めてみましょう!

util.js

公式の Vue デモからわかるように、Vue 単体テストではコンポーネントを Vue インスタンスにインスタンス化する必要があり、場合によってはそれを DOM にマウントする必要があります。

 const Constructor = Vue.extend(Hello) // 获得Hello组件实例
 const vm = new Constructor().$mount() // 将组件挂载到DOM上

上記の記述方法は単にコンポーネントを取得するためのものであり、場合によっては props 属性やカスタム メソッドなどを渡す必要があり、サードパーティの UI フレームワークを使用する必要がある場合もあります。したがって、上記の書き方は非常に面倒です。

ここでは、Vue 単体テストで一般的に使用されるメソッドをカプセル化する、Element の単体テスト ツール スクリプト Util.js をお勧めします。次のデモも Util.js に基づいて作成されています。
各メソッドの目的について簡単に説明します。

/**
 * 回收 vm,一般在每个测试脚本测试完成后执行回收vm。
 * @param {Object} vm
 */
exports.destroyVM = function (vm) {}

/**
 * 创建一个 Vue 的实例对象
 * @param {Object|String} Compo   - 组件配置,可直接传 template
 * @param {Boolean=false} mounted  - 是否添加到 DOM 上
 * @return {Object} vm
 */
exports.createVue = function (Compo, mounted = false) {}

/**
 * 创建一个测试组件实例
 * @param {Object} Compo     - 组件对象
 * @param {Object} propsData   - props 数据
 * @param {Boolean=false} mounted - 是否添加到 DOM 上
 * @return {Object} vm
 */
exports.createTest = function (Compo, propsData = {}, mounted = false) {}

/**
 * 触发一个事件
 * 注: 一般在触发事件后使用 vm.$nextTick 方法确定事件触发完成。
 * mouseenter, mouseleave, mouseover, keyup, change, click 等
 * @param {Element} elm   - 元素
 * @param {String} name   - 事件名称
 * @param {*} opts      - 配置项
 */
exports.triggerEvent = function (elm, name, ...opts) {}

/**
 * 触发 “mouseup” 和 “mousedown” 事件,既触发点击事件。
 * @param {Element} elm   - 元素
 * @param {*} opts     - 配置选项
 */
exports.triggerClick = function (elm, ...opts) {}

例 1

例 1 では、Hello コンポーネントのさまざまな要素のデータをテストし、util.js の destroyVM メソッドと createTest メソッドの使用法、およびテスト用のターゲット要素を取得する方法を学びました。 DOM 要素の取得方法については、「DOM オブジェクトのチュートリアル」を参照してください。

Hello.vue

<template>
 <p class="hello">
  <h1 class="hello-title">{{ msg }}</h1>
  <h2 class="hello-content">{{ content }}</h2>
 </p>
</template>

<script>
export default {
 name: &#39;hello&#39;,
 props: {
  content: String
 },
 data () {
  return {
   msg: &#39;Welcome!&#39;
  }
 }
}
</script>

Hello.spec.js

import { destroyVM, createTest } from &#39;../util&#39;
import Hello from &#39;@/components/Hello&#39;

describe(&#39;Hello.vue&#39;, () => {
 let vm

 afterEach(() => {
  destroyVM(vm)
 })

 it(&#39;测试获取元素内容&#39;, () => {
  vm = createTest(Hello, { content: &#39;Hello World&#39; }, true)
  expect(vm.$el.querySelector(&#39;.hello h1&#39;).textContent).to.equal(&#39;Welcome!&#39;)
  expect(vm.$el.querySelector(&#39;.hello h2&#39;).textContent).to.have.be.equal(&#39;Hello World&#39;)
 })

 it(&#39;测试获取Vue对象中数据&#39;, () => {
  vm = createTest(Hello, { content: &#39;Hello World&#39; }, true)
  expect(vm.msg).to.equal(&#39;Welcome!&#39;)
  // Chai的语言链是无意义的,可以随便写。如下:
  expect(vm.content).which.have.to.be.that.equal(&#39;Hello World&#39;) 
 })

 it(&#39;测试获取DOM中是否存在某个class&#39;, () => {
  vm = createTest(Hello, { content: &#39;Hello World&#39; }, true)
  expect(vm.$el.classList.contains(&#39;hello&#39;)).to.be.true
  const title = vm.$el.querySelector(&#39;.hello h1&#39;)
  expect(title.classList.contains(&#39;hello-title&#39;)).to.be.true
  const content = vm.$el.querySelector(&#39;.hello-content&#39;)
  expect(content.classList.contains(&#39;hello-content&#39;)).to.be.true
 })
})

出力結果

Hello.vue
√ 要素のコンテンツを取得するテスト
√ Vue オブジェクト内のデータを取得するテスト
√ クラスかどうかを取得するテストDOM に存在します

例 2

例 2 では、createTest を使用してクリック イベントをテストするテスト コンポーネントを作成し、createVue を使用して Vue サンプル オブジェクトを作成して Click コンポーネントの使用をテストします。ここでは主に createVue メソッドの使用方法を確認できます。

Click.vue

<template>
 <p>
  <span class="init-num">初始值为{{ InitNum }}</span><br>
  <span class="click-num">点击了{{ ClickNum }}次</span><br>
  <span class="result-num">最终结果为{{ ResultNum }}</span><br>
  <button @click="add">累加{{ AddNum }}</button>
 </p>
</template>

<script>
export default {
 name: &#39;Click&#39;,
 props: {
  AddNum: {
   type: Number,
   default: 1
  },
  InitNum: {
   type: Number,
   default: 1
  }
 },
 data () {
  return {
   ClickNum: 0,
   ResultNum: 0
  }
 },
 mounted () {
  this.ResultNum = this.InitNum
 },
 methods: {
  add () {
   this.ResultNum += this.AddNum
   this.ClickNum++
   this.$emit(&#39;result&#39;, {
    ClickNum: this.ClickNum,
    ResultNum: this.ResultNum
   })
  }
 }
}
</script>

Click.spec.js

import { destroyVM, createTest, createVue } from &#39;../util&#39;
import Click from &#39;@/components/Click&#39;

describe(&#39;click.vue&#39;, () => {
 let vm

 afterEach(() => {
  destroyVM(vm)
 })

 it(&#39;测试按钮点击事件&#39;, () => {
  vm = createTest(Click, {
   AddNum: 10,
   InitNum: 11
  }, true)
  let buttonElm = vm.$el.querySelector(&#39;button&#39;)
  buttonElm.click()
  buttonElm.click()
  buttonElm.click()
  // setTimeout 的原因
  // 在数据改变之后,界面的变化会有一定延时。不用timeout有时候会发现界面没有变化
  setTimeout(done => {
   expect(vm.ResultNum).to.equal(41)
   expect(vm.$el.querySelector(&#39;.init-num&#39;).textContent).to.equal(&#39;初始值为11&#39;)
   expect(vm.$el.querySelector(&#39;.click-num&#39;).textContent).to.equal(&#39;点击了3次&#39;)
   expect(vm.$el.querySelector(&#39;.result-num&#39;).textContent).to.equal(&#39;最终结果为41&#39;)
   done()
  }, 100)
 })

 it(&#39;测试创建Vue对象&#39;, () => {
  let result
  vm = createVue({
   template: `
    <click @click="handleClick"></click>
   `,
   props: {
    AddNum: 10,
    InitNum: 11
   },
   methods: {
    handleClick (obj) {
     result = obj
    }
   },
   components: {
    Click
   }
  }, true)
  vm.$el.click()
  vm.$nextTick(done => {
   expect(result).to.be.exist
   expect(result.ClickNum).to.equal(1)
   expect(result.ResultNum).to.be.equal(21)
   done()
  })
})

出力結果

click.vue
√ ボタンクリックイベントのテスト
√ Vueオブジェクトの作成テスト

その他

すべてのサンプルコードはGithubに配置されていますリポジトリが見やすい。さらに優れたテスト ケースを確認したい場合は、Util.js を使用して Element の単体テスト スクリプトの作成方法を確認することをお勧めします。学習すべきテスト スクリプトは数多くあります。大多数の Vue ユーザーが使用する UI コンポーネント ライブラリであるため、テスト スクリプトは非常によく書かれている必要があります。これらのスクリプトをコピーすることもでき、Vue コンポーネントの単体テストを学習するのに非常に役立つと思います。

以下は、参考のために要素単体テストを読んだときのメモです。

Util.js 方法包含了大多数Vue组件化的测试需求。

vm.$el vm.$nextTick 和 vm.$ref 都是在测试过程中比较常用的一些Vue语法糖。

需要注意: vm.$nextTick 方法是异步的,所以需要在里面使用done方法。

异步断言,方法参数需要是 _ 或者 done

大多数时候查询元素通过 querySelector 方法查询class获得

vm.$el.querySelector(&#39;.el-breadcrumb&#39;).innerText

大多数情况下查询是否存在某个Class通过 classList.contains 方法获得,查找的结果为 true 或 false

vm.$el .classList.contains(&#39;el-button--primary&#39;)

异步测试必须以 done() 方法结尾。setTimeout 和 vm.$nextTick 是常用的异步测试。

实现按钮点击:通过获取按钮元素 btn,执行 btn.click() 方法实现。

由于 Vue 进行异步更新DOM 的情况,一些依赖DOM更新结果的断言必须在 Vue.nextTick 回调中进行。

triggerEvent(vm.$refs.cascader.$el, &#39;mouseenter&#39;);
vm.$nextTick(_ => {
   vm.$refs.cascader.$el.querySelector(&#39;.el-cascader__clearIcon&#39;).click();
   vm.$nextTick(_ => {
    expect(vm.selectedOptions.length).to.be.equal(0);
    done();
   });
});

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在mint-ui中使用时间插件及获取选择值

VUE2实现二级省市联动选择

使用react实现分页组件

以上がVue単体テストにおけるKarma+Mochaの詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。