前端自動化測試(三)

前端自動化測試(一)
前端自動化測試(二)javascript


經過前兩篇文章的學習,我相信你們對Jest的核心用法能夠說是已經掌握了,這一篇文中咱們在Vue中使用Jestcss

1.Vue中集成Jest

咱們能夠經過vue官方提供的@vue/cli 直接建立Vue項目,在建立前須要先安裝好@vue/cli~前端

這裏直接建立項目:vue

vue create vue-unit-project
? Please pick a preset:
  default (babel, eslint)
❯ Manually select features # 手動選擇
? Check the features needed for your project:
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
❯◉ Unit Testing
 ◯ E2E Testing
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, Unit
? Use history mode for router?  # history模式
ion) Yes
? Pick a unit testing solution: Jest # 測試框架選擇Jest
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config  # 將配置文件產生獨立的文件
 files
? Save this as a preset for future projects? (y/N) # 是否保存配置

初始化成功後,咱們先來查看項目文件,由於咱們主要關注的是測試,因此先查看jest.config.js文件java

module.exports = {
  moduleFileExtensions: [ // 測試的文件類型
    'js','jsx','json','vue'
  ],
  transform: { // 轉化方式
    '^.+\\.vue$': 'vue-jest', // 若是是vue文件使用vue-jest解析
    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', // 若是是圖片樣式則使用 jest-transform-stub
    '^.+\\.jsx?$': 'babel-jest' // 若是是jsx文件使用 babel-jest
  },
  transformIgnorePatterns: [ // 轉化時忽略 node_modules
    '/node_modules/'
  ],
  moduleNameMapper: { // @符號 表示當前項目下的src
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  snapshotSerializers: [ // 快照的配置
    'jest-serializer-vue'
  ],
  testMatch: [ // 默認測試 /test/unit中包含.spec的文件 和__tests__目錄下的文件
    '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
  ],
  testURL: 'http://localhost/', // 測試地址
  watchPlugins: [ // watch提示插件
    'jest-watch-typeahead/filename',
    'jest-watch-typeahead/testname'
  ]
}

經過配置文件的查看咱們知道了全部測試都應該放在tests/unit目錄下!node

咱們能夠查看pacakge.json來執行對應的測試命令ios

"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "test:unit": "vue-cli-service test:unit --watch" // 這裏增長個 --watch參數
},

開始測試 npm run test:unitvue-cli

2.測試Vue組件

咱們先忽略默認example.spec.js文件,本身來嘗試下如何測試Vue組件npm

2.1 測試HelloWorld組件

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

HelloWorld組件須要提供一個msg屬性,將msg屬性渲染到h1標籤中,ok咱們來編寫測試用例json

tests/unit下建立 HelloWorld.spec.js

import Vue from 'vue';
import HelloWorld from '@/components/HelloWorld'
describe('測試HelloWolrd 組件',()=>{
    it('傳入 msg 屬性看可否渲染到h1標籤內',()=>{
        const  baseExtend = Vue.extend(HelloWorld);
        // 獲取當前組件的構造函數,而且掛載此組件
        const vm = new baseExtend({
            propsData:{
                msg:'hello'
            }
        }).$mount();
        expect(vm.$el.innerHTML).toContain('hello');
    })
});

這樣一個簡單的Vue組件就測試成功了!可是寫起來感受不簡潔也不方便,因此爲了更方便的測試,Vue官方提供給咱們了個測試工具Vue Test Utils,並且這個工具爲了方便應用,採用了同步的更新策略。

import Vue from 'vue';
import HelloWorld from '@/components/HelloWorld';
import {shallowMount} from '@vue/test-utils'
describe('測試HelloWolrd 組件',()=>{
    it('傳入 msg 屬性看可否渲染到h1標籤內',()=>{
        const wrapper = shallowMount(HelloWorld,{
            propsData:{
                msg:'hello'
            }
        })
        expect(wrapper.find('h1').text()).toContain('hello')
    });
});

這樣寫測試是否是很high,能夠直接渲染組件傳入屬性,默認返回wrapperwrapper上提供了一系列方法,能夠快速的獲取DOM元素! 其實這個測試庫的核心也是在 wrapper的方法上,更多方法請看 Vue Test Utils

這裏的shallowMount被譯爲潛渲染,也就是說HelloWorld中引入其餘組件是會被忽略掉的,固然也有深度渲染mount方法!

剛纔寫測試的這種方式就是先編寫功能!編寫完成後,咱們來模擬用戶的行爲進行測試,並且只測試其中的某個具體的功能!這就是咱們所謂的 BDD形式的單元測試。接下來,咱們再來換種思路寫個組件!

2.2 測試Todo組件

這回呢,咱們採用TDD的方式來測試,也就是先編寫測試用例

先指定測試的功能: 咱們要編寫Todo組件

  • 當輸入框輸入內容時會將數據映射到組件實例上
  • 若是輸入框爲空則不能添加,不爲空則新增一條
  • 增長的數據內容爲剛纔輸入的內容

編寫Todo.spec.js

import Todo from '@/components/Todo.vue';
import {shallowMount} from '@vue/test-utils'
describe('測試Todo組件',()=>{
    it('當輸入框輸入內容時會將數據映射到組件實例上',()=>{
        // 1) 渲染Todo組件
        let wrapper = shallowMount(Todo);
        let input = wrapper.find('input');
        // 2.設置value屬性 並觸發input事件
        input.setValue('hello world');
        // 3.看下數據是否被正確替換
        expect(wrapper.vm.value).toBe('hello world')
    });
    it('若是輸入框爲空則不能添加,不爲空則新增一條',()=>{
        let wrapper = shallowMount(Todo);
        let button = wrapper.find('button');
        // 點擊按鈕新增一條
        wrapper.setData({value:''});// 設置數據爲空
        button.trigger('click');
        expect(wrapper.findAll('li').length).toBe(0);
        wrapper.setData({value:'hello'});// 寫入內容
        button.trigger('click');
        expect(wrapper.findAll('li').length).toBe(1);
    });
    it('增長的數據內容爲剛纔輸入的內容',()=>{
        let wrapper = shallowMount(Todo);
        let input = wrapper.find('input');
        let button = wrapper.find('button');
        input.setValue('hello world');
        button.trigger('click');
        expect(wrapper.find('li').text()).toMatch(/hello world/);
    });
});

咱們爲了跑通這些測試用例,只能被迫寫出對應的代碼!

<template>
 <div>
  <input type="text" v-model="value" />
  <button @click="addTodo"></button>
  <ul>
   <li v-for="(todo,index) in todos" :key="index">{{todo}}</li>
  </ul>
 </div>
</template>
<script>
export default {
 methods: {
  addTodo() {
   this.value && this.todos.push(this.value)
  }
 },
 data() {
  return {
   value: "",
   todos: []
  };
 }
};
</script>

以上就是咱們針對Todo這個組件進行的單元測試,可是真實的場景中可能會更加複雜。在真實的開發中,咱們可能將這個Todo組件進行拆分,拆分紅TodoInput組件和TodoList組件和TodoItem組件,若是採用單元測試的方式,就須要依次測試每一個組件(單元測試是以最小單元來測試的) 可是單元測試沒法保證整個流程是能夠跑通的,因此咱們在單元測試的基礎上還要採用集成測試

總結:

  • 1.單元測試能夠保證測試覆蓋率高,可是相對測試代碼量大,缺點是沒法保證功能正常運行
  • 2.集成測試粒度大,廣泛覆蓋率低,可是能夠保證測試過的功能正常運行
  • 3.通常業務邏輯會採用BDD方式採用集成測試(像測試某個組件的功能是否符合預期),通常工具方法會採用TDD的方式使用單元測試
  • 4.對於 UI 組件來講,咱們不推薦一味追求行級覆蓋率,由於它會致使咱們過度關注組件的內部實現細節,從而致使瑣碎的測試

2.3 測試Vue中的異步邏輯

在測試Vue項目中,咱們可能會在組件中發送請求,這時咱們仍然須要對請求進行mock

<template>
  <ul>
   <li v-for="(list,index) in lists" :key="index">{{list}}</li>
  </ul>
</template>
<script>
import axios from 'axios'
export default {
 async mounted(){
    let {data} = await axios.get('/list');
    this.lists = data;
 },
 data() {
  return {
   lists: []
  };
 }
};
</script>

能夠參考上一篇文章:如何實現jest進行方法的mock

import List from "@/components/List.vue";
import { shallowMount } from "@vue/test-utils";
jest.mock("axios");
it("測試List組件", done => {
  let wrapper = shallowMount(List);
  setTimeout(() => {
    expect(wrapper.findAll("li").length).toBe(3);
    done();
  });
});
這裏使用setTimeout的緣由是咱們本身mock的方法是promise,因此是微任務。咱們指望微任務執行後再進行斷言,因此採用setTimeout進行包裹,保證微任務已經執行完畢。若是組件中使用的不是 async、await形式,也能夠使用 $nextTick, (新版node中 await後的代碼會延遲到下一輪微任務執行)。

舉個栗子:

function fn(){
    return new Promise((resolve,reject)=>{
        resolve([1,2,3]);
    })
}
async function getData(){
    await fn();
    // await fn()  會編譯成
    // new Promise((resolve)=>resolve(fn())).then(()=>{
    //     console.log(1)
    // })
    console.log(1);
}
getData();
Promise.resolve().then(data=>{
    console.log(2);
});
固然不一樣版本執行效果可能會有差別

來簡單看下不是async、await的寫法~~~

axios.get('/list').then(res=>{
    this.lists = res.data;
})
it('測試List組件',()=>{
    let wrapper = shallowMount(List);
    // nextTick方法會返回一個promise,由於微任務是先進先出,因此nextTick以後的內容,會在數據獲取以後執行
    return wrapper.vm.$nextTick().then(()=>{
        expect(wrapper.vm.lists).toEqual([1,2,3])
    })
})

2.4 測試Vue中的自定義事件

咱們寫了一個切換顯示隱藏的組件,當子組件觸發change事件時能夠切換p標籤的顯示和隱藏效果

<template>
    <div>
        <Head @change="change"></Head>
        <p v-if="visible">這是現實的內容</p>
    </div>
</template>
<script>
import Head from './Head'
export default {
    methods:{
        change(){
            this.visible = !this.visible;
        }
    },
    data(){
        return {visible:false}
    },
    components:{
        Head
    }
}
</script>

咱們來測試它!能夠直接經過wrapper.find方法找到對應的組件發射事件。

import Modal from '@/components/Modal';
import Head from '@/components/Head';
import {mount, shallowMount} from '@vue/test-utils'
it('測試 觸發change事件後 p標籤是否能夠切換顯示',()=>{
    let wrapper = shallowMount(Modal);
    let childWrapper = wrapper.find(Head);
    expect(wrapper.find('p').exists()).toBeFalsy()
    childWrapper.vm.$emit('change');
    expect(childWrapper.emitted().change).toBeTruthy(); // 檢驗方法是否被觸發
    expect(wrapper.find('p').exists()).toBeTruthy(); // 檢驗p標籤是否顯示
})

到這裏咱們對vue的組件測試已經基本搞定了,接下來咱們再來看下如何對Vue中的VuexVue-router進行處理

往期回顧:
前端自動化測試(一)
前端自動化測試(二)


圖片描述

相關文章
相關標籤/搜索