單元測試與單元測試框架 Jest

什麼是單元測試?

測試是一種驗證咱們的代碼是否能夠按預期工做的手段。react

被測試的對象能夠是咱們程序的任何一個組成部分。大到一個分爲多步驟的下單流程,小到代碼中的一個函數。程序員

單元測試特指被測試對象爲程序中最小組成單元的測試。這裏的最小組成單元能夠是一個函數、一個類等等。面試

單元測試的優點

因爲被測試對象的簡單(一般只有一個或多個輸入以及一個輸出),這就決定了單元測試開發起來也很簡單,一般每一個測試只有幾行到十幾行不等。測試代碼的簡單表示它能夠被更頻繁的執行(事實上,不少單元測試框架都有 watch 模式。每次改動代碼時都會自動執行單元測試)。更頻繁的執行意味着更早的發現問題。npm

試想,隨着代碼的不斷迭代,程序中總有某些位置會頻繁出現某類問題。在沒有單元測試時程序員之間每每都是「口口相傳」,隔一段時間極可能因爲疏忽還會犯同一個錯誤。有了單元測試咱們就能夠爲這些問題點編寫對應的測試代碼,每次提交代碼前都執行一遍,能夠極大的下降相同 bug 重複出現的機率。json

此外,要爲一個被測試對象編寫單元測試,那麼它應該首先是容易被測試的(這彷佛是一句廢話)。反過來說,若是你面對一個函數、類卻很難編寫測試代碼的時候,極可能是你的代碼設計上存在問題。好比和外部依賴耦合過於緊密。這種狀況下,編寫單元測試的過程會倒逼咱們優化咱們代碼的結構。將複雜的代碼拆解成爲更簡單、更容易測試的片斷。這個過程自己也會潛移默化的提升咱們代碼的質量。react-native

單元測試的限制/不足

I get paid for code that works, not for tests - Kent Beck數組

首先,測試代碼再簡單,也是須要工做量來開發的。一定佔用開發人員的時間。所以須要開發人員在投入與收益之間找到一個最佳的平衡點。服務器

其次,單元測試覆蓋率每每會給開發人員一種錯覺:這段代碼的單元測試都經過了(測試覆蓋率以及 100% 了),確定沒有 bug。其實否則,單元測試覆蓋率與代碼質量沒有必然的聯繫。做爲開發人員必須儘早認識到這一點。app

什麼時候編寫單元測試?

  • 開發過程當中,單元測試應該來測試那些可能會出錯的地方,或是那些邊界狀況。
  • 維護過程當中,單元測試應該圍繞着 bug 進行,每一個 bug 都應該編寫響應的單元測試。從而保證同一個 bug 不會出現第二次。

單元測試中的基本概念?

單元測試通常包含如下幾個部分:框架

  • 被測試的對象是什麼
  • 要測試該對象的什麼功能
  • 實際獲得的結果
  • 指望的結果
  • mock / spy (下文會詳述)

具體到某個單元測試,每每包含如下幾個步驟:

  • 準備階段:構造參數,建立 spy 等
  • 執行階段:用構造好的參數執行被測試代碼
  • 斷言階段:用實際獲得的結果與指望的結果比較,以判斷該測試是否正常
  • 清理階段:清理準備階段對外部環境的影響,移除在準備階段建立的 spy 等

Jest 簡介

Jest是 Facebook 開發的一款 JavaScript 測試框架。在 Facebook 內部普遍用來測試各類 JavaScript 代碼。其官網上主要列出瞭如下幾個特色:

  • 輕鬆上手
    • 使用 create-react-app或是 react-native init建立的項目已經默認集成了 Jest
    • 現有項目,只需建立一個名爲 __test__ 的目錄,而後在該目錄中建立以 .spec.js 或 .test.js 結尾的文件便可
  • 內置強大的斷言與 mock 功能
  • 內置測試覆蓋率統計功能
  • 內置 Snapshot 機制

雖然 Jest 官網介紹中屢次 React,但實際上 Jest 並非和 React 綁定的。你可使用它測試任何 JavaScript 項目。

Jest 基礎功能介紹

安裝:

npm install --save-dev jest

而後配置 package.json :

"scripts": {
  "test": "jest --color"
}

接着建立一個名爲 __tests__ 的目錄。jest 會自動去該目錄下尋找要執行的測試代碼。

接下來讓咱們編寫一個最簡單測試。

describe('Addition', () => {
  it('knows that 2 and 2 make 4', () => {
    const val1 = 2;
    const val2 = 2;
    const result = val1 + val2;
    const expectedResult = 4;
    expect(result).toBe(expectedResult);
  });
});

接下來讓咱們看看這個單元測試是否知足了咱們前文提到的元素與步驟。

元素:

  • 被測試的對象是什麼: + 運算符
  • 要測試該對象的什麼功能: 2 + 2 = 4
  • 實際獲得的結果:result
  • 指望的結果: expectedResult

步驟:

  • 準備階段:line3, line4
  • 執行階段:line5
  • 斷言階段:line7
  • 清理階段:無

能夠看出,單元測試的編寫是有「套路」可循的。實際中,咱們通常不會建立這麼多臨時變量,能夠簡寫成:

describe('Addition', () => {
  it('knows that 2 and 2 make 4', () => {
      expect(2 + 2).toBe(4);
  });
});

toBe 只是 Jest 強大斷言功能中的一個方法。

如今讓咱們來執行一下剛剛編寫的測試代碼吧:

Jest 中的 mock 與 spy

讓咱們來經過例子瞭解 mock 與 spy。

假設有下面這個函數:

function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}

功能很簡單,循環第一個參數 items,並把數組中的每一項做爲參數調用第二個參數 callback。該如何測試呢?

咱們要建立一個特殊的 callback 函數,它能夠記錄每次調用時傳入的參數供咱們進行斷言。

下面是一段示例代碼:

describe('forEach', () => {
  it('should call callback with each item', () => {
    const callHistory = [];
    const specialCallback = (...args) => callHistory.push(args);
    forEach([1, 2], specialCallback);

    expect(callHistory.length).toBe(2);
    expect(callHistory[0][0]).toBe(1);
    expect(callHistory[1][0]).toBe(2);
  })
});

這裏的 specialCallback 就是一個 mock。它存在的意義就是統計函數被調用的信息供咱們使用。這種模式在單元測試中常常被使用,因此 Jest 已經內置了對 mock 的支持。讓咱們來看看如何使用:

describe('forEach', () => {
  it('should call callback with each item', () => {
    const mockFn = jest.fn();
    forEach([1, 2], mockFn);

    expect(mockFn.mock.calls.length).toBe(2);
    expect(mockFn.mock.calls[0][0]).toBe(1);
    expect(mockFn.mock.calls[1][0]).toBe(2);
  })
});

很方便吧,只須要 jest.fn() 一下就能夠獲得一個功能搶到的 mock 函數。

最後再來講一下 spy。其實 spy 和 mock 是很是相似的,惟一的區別點在於,spy 用於監聽一個現有對象上的方法。

仍是經過一個例子來看,假設咱們有對象:

const bot = {
  sayHello: (name) => {
    console.log(`Hello ${name}!`);
  }
}

咱們能夠像下面這樣建立並使用 spy:

describe('bot', () => {
  it('should say hello', () => {
    const spy = jest.spyOn(bot, 'sayHello');

    bot.sayHello('Michael');

    expect(spy).toHaveBeenCalledWith('Michael');

    spy.mockRestore();
  })
});

咱們經過 jest.spyOn 建立了一個監聽 bot 對象的 sayHello 方法的 spy。它就像間諜同樣監聽了全部對 bot#sayHello 方法的調用。因爲建立 spy 時,Jest 實際上修改了 bot 對象的 sayHello 屬性,因此在斷言完成後,咱們還要經過 mockRestore 來恢復 bot 對象本來的 sayHello 方法。

實戰:使用 Jest 編寫一個完整的單元測試

到這裏,單元測試的套路和 Jest 的基本用法已經介紹的差很少了。讓咱們最後經過一個完整的示例來結束今天的討論。

被測試的函數名爲 getImageDomain。主要功能就是爲某個 skuId 選取一個圖片服務器域名,若是未傳入 skuId,則隨機返回一個域名:

const domains = [
  'img10.360buyimg.com',
  'img11.360buyimg.com',
  'img12.360buyimg.com',
  'img13.360buyimg.com',
  'img14.360buyimg.com',
];

const getImageDomain = (skuId) => {
  if (skuId) {
    return domains[skuId % 5];
  } else {
    return domains[Math.floor(Math.random() * 5)];
  }
}

對應的測試代碼以下,因爲邏輯比較簡單,故再也不詳細分析:

describe('getImageDomain', () => {
  it('should select domain based on skuId if provided', () => {
    expect(getImageDomain(1)).toBe('img11.360buyimg.com');
  });

  it('should select a random domain based on Math.random if skuId not available', () => {
    const spy = jest.spyOn(Math, 'random').mockImplementation(() => 0.9);

    expect(getImageDomain()).toBe('img14.360buyimg.com');
    expect(spy).toHaveBeenCalled();

    spy.mockRestore();
  });
});

寫在最後

測試只是一種手段,而不是目的。

軟件的質量不是測試出來的,而是設計和維護出來的。

以上內容就是本篇的所有內容以上內容但願對你有幫助,有被幫助到的朋友歡迎點贊,評論。若是對軟件測試、接口測試、自動化測試、面試經驗交流。感興趣能夠關注博主,咱們會有同行一塊兒技術交流哦。

相關文章
相關標籤/搜索