前端需要寫自動化測試嗎?那又該怎麼寫呢?【建議收藏】

語言: CN / TW / HK

為什麼需要寫前端自動化

大部分企業為了追求開發效率,所以並沒有去強制要求員工寫前端自動化測試的程式碼。另一部分企業則會要求前端開發額外寫前端自動化測試。那麼寫和不寫到底有哪些區別呢?

不寫前端自動化測試程式碼:

  • 修改某個模組功能時,其它模組也受影響,很難快速定位bug
  • 多人開發程式碼越來越難以維護
  • 不方便迭代,程式碼重構困難
  • 程式碼質量差,參差不齊

增加自動化測試後:

  • 我們為核心功能編寫測試後可以保障專案的可靠性
  • 強迫開發者編寫更容易被測試的程式碼,提高程式碼質量
  • 編寫的測試有文件的作用,方便維護
  • 開發速度有所變慢,因為要多寫一份測試程式碼(手動滑稽)

實際上僅僅就為了可靠性、碼質量、可維護性是完全值得你去寫前端自動化的。作為一名前端開發人員掌握自動化測試技術是必不可少的,就算你工作中不用,也可以放在你的簡歷中,它是一道靚麗的風景線。

測試介紹

測試在工作中分為以下兩個大塊:

黑盒測試和白盒測試

  • 黑盒測試一般也被稱為功能測試,黑盒測試要求測試人員將程式看作一個整體,不考慮其內部結構和特性,只是按照期望驗證程式是否能正常工作。(薪資12k見頂)
  • 白盒測試是基於程式碼本身的測試,一般指對程式碼邏輯結構的測試。(薪資10k起)

測試分類

單元測試(Unit Testing)

單元測試是指對程式中最小可測試單元進行的測試,例如測試一個函式、一個模組、一個元件...

整合測試(Integration Testing)

將已測試過的單元測試函式進行組合整合暴露出的高層函式或類的封裝,對這些函式或類進行的測試。

端到端測試(E2E Testing)

開啟應用程式模擬輸入,檢查功能以及介面是否正確。

不過作為一名前端,我們需要寫哪些測試呢?答案是:

  • 單元測試
  • 端到端測試(E2E)

接下來就帶你們來實踐一下。

新建專案

我們新建個vue2的專案,選擇下面這些 注意我們到了選擇單元測試框架這一步的時候,選擇jest: 然後端對端我們選擇: 這樣繼續安裝就好了,直到專案建好。

單元測試

單元測試(unit testing),是指對軟體中的最⼩小可測試單元進⾏行行檢查和驗證。例如一個函式。 單測針對元件 或者函式 或者模組(開發人員知道具體邏輯)

前面建專案的時候提到了jest,實際上在vue中,推薦⽤ Mocha+chai 或者jest來做單元測試,但咱們這邊使⽤用 jest 來演示,兩者語法基本⼀致。

測試某個函式

在src目錄下建立utils目錄,再在utils下建立index.js,並寫個add函式匯出:

export function add(x,y) {
    return x + y
}

在tests下的unit下的example.spec.js中修改(當然也可以新建一個*.spec.js,固定格式的檔案)

// import { shallowMount } from '@vue/test-utils'
// import HelloWorld from '@/components/HelloWorld.vue'

// describe('HelloWorld.vue', () => {
//   it('renders props.msg when passed', () => {
//     const msg = 'new message'
//     const wrapper = shallowMount(HelloWorld, {
//       propsData: { msg }
//     })
//     expect(wrapper.text()).toMatch(msg)
//   })
// })

import { add } from "@/utils/index.js";
describe("測試加法函式", () => {
  //測試程式碼可讀性最好
  //分組
  it("一個具體的功能測試,測測試數字相加", () => {
    expect(add(1, 2)).toBe(3);
  });
  it("一個具體的功能測試,測測試數字和字串相加", () => {
    expect(add("a", 2)).toBe("a2");
  });
  it("一個具體的功能測試,測測試數字字串相加", () => {
    expect(add("1", 2)).toBe(3);
  });
});

執行:

npm run test:unit

顯然"1" + 2 不是3,所以測試是報了錯的。

這個案例我們就用到了4個api。

api介紹

  • describe : 定義⼀一個測試套件
  • it :定義⼀一個測試⽤用例例
  • expect :斷⾔言的判斷條件
  • toBe :斷⾔言的⽐比較結果

測試vue元件

components下新建 zhifeiji.vue 檔案:

<template>
  <div>
    <span>{{ msg }}</span>
    <span>{{ msg1 }}</span>
    <button class="btn" @click="changeMsg">點我</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: "vue test",
      msg1: "你好",
    };
  },
  created() {
    this.msg = "aftermounted";
  },
  mounted() {
    this.msg1 = "測試下vue元件";
  },
  methods: {
    changeMsg() {
      this.msg = "click over";
    },
  },
};
</script>

views目錄下的home元件中引入:

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <zhifeiji_comp></zhifeiji_comp>
  </div>
</template>

<script>
// @ is an alias to /src
// import HelloWorld from "@/components/HelloWorld.vue";
import zhifeiji_comp from "@/components/zhifeiji.vue";

export default {
  name: "home",
  components: {
    // HelloWorld,
    zhifeiji_comp,
  },
};
</script>

在tests目錄下的unit目錄下建立 zhifeiji.spec.js:

import Vue from "vue";
import zhifeijiComp from "@/components/zhifeiji.vue";
import { mount } from "@vue/test-utils";

describe("測試zhifeiji元件", () => {
  it("測試初始化的data", () => {
    expect(zhifeijiComp.data().msg).toBe("vue test");
  });
  //created和mounted裡資料測試都是一樣的
  it("測試新建完畢後,create生命週期後的資料", () => {
    //created
    let vm = new Vue(zhifeijiComp).$mount();
    expect(vm.msg).toBe("aftermounted");
  });
  it("測試新建完畢後,create生命週期後的資料", () => {
    //mounted
    let vm = new Vue(zhifeijiComp).$mount();
    expect(vm.msg1).toBe("測試下vue元件");
  });
  //點選事件測試
  it("測試點選後,msg的改變", () => {
    // $mount處理不了使用者互動,所以我們要用到vue官方推薦的@vue/test-utils
    let wrapper = mount(zhifeijiComp);
    expect(wrapper.vm.msg).toBe("aftermounted");
    //點選一下
    wrapper.find(".btn").trigger("click");
    expect(wrapper.vm.msg).toBe("click over");
  });
});

如果測試使用者互動的話,需要用到官方推薦的@vue/test-utils,執行cnpm i @vue/test-utils --save,相關文件在vue官網

在 @vue/test-utils 中引入mount替代vue的 $mount 的是因為 $mount 的是虛擬的,存在虛擬記憶體中,處理不了dom,所以用mount(對不對我不知道,這句話僅供參考)

到這裡為止的話,我這個vue元件的測試,應該是通過的才對,執行npm run test:unit驗證一下:

測試覆蓋率

jest⾃自帶覆蓋率,如果⽤用的 mocha,需要使⽤用 istanbul來統計覆蓋率。(所以我推薦jest)

npm run test:unit

這個明顯是不通過的,需要將途中的3改為正確的‘12’。 改完後再次執行命令。 這麼一來測試是通過了,但是好像無法直觀看到覆蓋率,所以我們來對package.json中的jest進行下修改,在下圖的位置中: 改為:

"jest": {
    "collectCoverage": true,
    "collectCoverageFrom": ["src/**/*.{js,vue}"],
    "moduleFileExtensions": [
      "js",
      "json",
      "vue"
    ],
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
      ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
    },
    "moduleNameMapper": {
      "^@/(.*)$": "<rootDir>/src/$1"
    },
    "snapshotSerializers": [
      "<rootDir>/node_modules/jest-serializer-vue"
    ],
    "testURL": "http://localhost"
  }

倘若有個jest.config.js那麼就在 moduleFileExtensions 上一行加入,這個檔案是對jest的配置(沒有不必改):

"collectCoverage": true,
"collectCoverageFrom": ["src/**/*.{js,vue}"]

改好後,我們再來執行下npm run test:unit命令, 到此,我們就能清晰看見測試的一個覆蓋情況!這個覆蓋率會生成一個報告,生成的報告檔案在coverage目錄下,開啟可以看到這麼個鬼東西: 這麼一來,測試程式碼覆蓋情況就一覽無遺了。

這個測試報告可以很精確的看到我們哪些沒測試,哪些測試了。

我在zhifeiji.vue里加點東西不測試,來看看結果:

<template>
  <div>
    <span id="msg">{{ msg }}</span>
    <span>{{ msg1 }}</span>
    <button class="btn" @click="changeMsg">點我</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: "vue test",
      msg1: "你好",
      msg2:'這個不測試'
    };
  },
  created() {
    this.msg = "aftermounted";
    this.msg2 = '不測試扣了我100塊';
  },
  mounted() {
    this.msg1 = "測試下vue元件";
  },
  methods: {
    changeMsg() {
        if(this.msg === '1'){
            this.msg = "click false";
        }else{
            this.msg = "click over";
        }
      
    },
  },
};
</script>

上面程式碼中msg2有關的都是沒測試的,執行下測試命令看報告: 紅框部分可見著這次zhifeiji.vue覆蓋率沒有100%了,上main藍色的我們可以點選進去。 一步步點進來,會來到被測試的程式碼檔案。

如何測試不通過就阻止程式碼git提交

安裝husky:

cnpm i husky –save
# yarn、cnpm、npm 隨意

配置husky,在package.json配置:

"husky": {
    "hooks": {
      "pre-commit": "npm test",
      "pre-push": "npm test",
      "...": "..."
    }
}

當然這是文件上的案例,我們改下:

"husky": {
    "hooks": {
      "pre-commit": "npm run test:unit"
    }
}

代表再提交(commit)前先執行npm run test:unit。

驗證 修改測試程式碼檔案zhifeiji.spec.js: 由:

it("測試新建完畢後,create生命週期後的資料", () => {
    //created
    let vm = new Vue(zhifeijiComp).$mount();
    expect(vm.msg).toBe("aftermounted");
});

改為:

it('測試新建完畢後,create生命週期後的資料',() => {
	//created
    let vm = new Vue(zhifeijiComp).$mount()
    expect(vm.msg).toBe('aftermounted1')
})

這樣測試肯定是不通過的。 這時,測試不通過,我們再來提交程式碼,add後再commit: 可見commit是失敗了,沒有通過!

Tip:如果你們這麼配置husky不生效,請降低husky版本或者使用新的husky的方式,husky在6.0.0版本開始就使用了新的配置方法,具體看此文:https://blog.csdn.net/MrWeb/article/details/119878688

這時我們如果想讓他通過的話就得改程式碼了,讓其正確了才能提交,這樣才是安全可靠的一份程式碼。

修改操作: 1.註釋掉tests目錄下e2e中的test.js,e2e我們後面講 2.example.spec.js中的3修改為'12' 3.zhifeiji.spec.js的aftermounted1恢復為aftermounted

經過這三步操作,我們就算是解決了測試的報錯,然後commit: 程式碼提交成功!

E2E測試

e2e針對應用,站在測試人員的角度,沒有什麼mount 載入,只有按鈕 頁面,輸入框,文字等

借⽤瀏覽器器的能力,站在⽤戶測試⼈員的角度,輸⼊框,點選按鈕等,完全模擬使用者,這個和具體的框架關係不大,完全模擬瀏覽器行為

將views下的Home元件的:

<HelloWorld msg="Welcome to Your Vue.js App"></HelloWorld>

相關程式碼恢復。 看看tests目錄下e2e目錄下的spec下的test.js,有這麼一段:

// https://docs.cypress.io/api/introduction/api.html

describe('My First Test', () => {
  it('Visits the app root url', () => {
    // cy.visit('/')
    // cy.contains('h1', 'Welcome to Your Vue.js App')
  })
})

將cy註釋的那塊恢復。

describe('My First Test', () => {
  it('Visits the app root url', () => {
    cy.visit('/')
    cy.contains('h1', 'Welcome to Your Vue.js App')
  })
})

這是測試程式碼,如果我們不做前面的恢復操作的話,測試肯定是不通過的

執行npm run test:e2e,進行測試

專案會啟動,並且會彈出一個有ok的彈窗,我們直接確定,還會有測試檔案的js,選擇對應的測試檔案點進去: 這樣代表測試通過了!

再來測試一個about頁:

// https://docs.cypress.io/api/introduction/api.html

describe('My First Test', () => {
  it('Visits the app root url', () => {
    cy.visit('/')
    cy.contains('h1', 'Welcome to Your Vue.js App')
  })
  it('測試about頁', () => {
    //訪問about
    cy.visit('about')
    cy.contains('h1', 'This is an about page')
  })
})

Tip:about頁面測試不通過的,請將路由模式改為history或者將測試檔案test.js中的cy.visit('about')改為cy.visit('#/about')

後測的about,就最後打開了about頁,也停留在了about頁面

這些都是頁面某個元素的文字的測試,那麼我們再來個互動試試,在我們的zhifeiji元件上有個點選事件,我們來試試:

describe('My First Test', () => {
  it('Visits the app root url', () => {
    cy.visit('/')
    cy.contains('h1', 'Welcome to Your Vue.js App')
  })
  it('測試about頁', () => {
    //訪問about
    cy.visit('#/about')
    cy.contains('h1', 'This is an about page')
  })
  it('zhifeiji元件',() => {
    //訪問根目錄
    cy.visit('/')
    cy.contains('#msg','aftermounted')
    cy.get('button').click()//點選button元素
    cy.contains('#msg','click over')
  })
})

左邊那一條條可以點選,點選不同的項,也會進入不同的事件狀態,例如我點了CONTAINS,文字變成了click over。

題外話:測試頁面前後差異,或者兩個頁面的差異可以用page-monitor;地址:https://github.com/fouber/page-monitor

好了,今天的前端自動化測試就講到這裡。

熱門推薦:

全棧開發微信公眾號

typescript入門

vue中使用JSX

前端基本知識