精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

前端自動化測試 之 Jest 測試框架應用

開發 前端
在實際業務應用中,我們建議對可復用的組件、工具函數、工具類等一些無副作用,可預知結果的代碼來進行單元測試。在前期開發過程中的投入會大于沒有單元測試的投入,因為要寫一些測試用例,還要執行測試用例,優化代碼等。但是在長久迭代中,這種方法會比沒有進行單元測試的模塊更加穩定。

前端自動化測試 —— Jest 測試框架應用

http://zoo.zhengcaiyun.cn/blog/article/jest

圖片

什么是自動化測試

在軟件測試中,自動化測試指的是使用獨立于待測軟件的其他軟件來自動執行測試、比較實際結果與預期并生成測試報告這一過程。在測試流程已經確定后,測試自動化可以自動執行的一些重復但必要的測試工作。也可以完成手動測試幾乎不可能完成的測試。對于持續交付和持續集成的開發方式而言,測試自動化是至關重要的。   ——來自 WiKi 百科

為什么要用前端自動化測試

隨著前端項目的發展,其規模和功能日益增加。為了提高項目的穩定性和可靠性,除了需要測試工程師外,前端自動化測試也成為了不可或缺的一環。采用前端自動化測試可以有效地提高代碼質量,降低出錯的概率,從而使項目更加健壯和更易維護。

前端自動化分類和思想

單元測試

又稱為模塊測試 ,是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工作。在前端中,一個函數、一個類、一個模塊文件,都可以進行單元測試,測試時每個模塊都是互不干擾的。

圖片

集成測試

是在單元測試的基礎上,測試再將所有的軟件單元按照概要設計規格說明的要求組裝成模塊、子系統或系統的過程中各部分工作是否達到或實現相應技術指標及要求的活動。用戶的開始操作到結束操作這一整個行為流程可以當作集成測試。

圖片

TDD 測試驅動開發(Test Driven Development)

開發流程:

圖片

TDD 是趨向于白盒測試,需要開發者對當前編寫的模塊思路足夠清晰。

優勢:

  1. 長期減少回歸 bug。
  2. 代碼質量更好,可維護性高。
  3. 測試覆蓋率高(先寫測試用例,再實現功能)。
  4. 錯誤測試代碼不容易出現(測試在開發之前執行)。

BDD 行為驅動開發(Behavior Driven Development)

開發流程:

圖片

BDD 趨向于黑盒測試,只關注用戶的一整套行為流程下來是否會成功。

優勢:

  1. 對于用戶行為的整個流程把控程度較高,對于開發人員來說這樣安全感高。

如何自己寫非框架測試用例

不使用測試框架,我們該如何測試自己的模塊呢?如果我們想要測試下面的代碼,應該需要兩個值,一個是 期望值 ,另一個是函數執行的 結果值 ,我們需要對比兩個值來進行判斷當前函數是否通過了測試用例。

// index.js
function ZcyZooTeam(str) {
  return 'Zcy' + str;
}

需要下面的 if / else 進行判斷當前的期望值 value 和結果值 result 是否相等,如果相等說明我們的測試用例通過了。我們將這兩段代碼復制到瀏覽器中,下面的執行不會通過,并會拋出錯誤,只有我們將傳入值改為 ZooTeam 才會成功執行。

// no-jest.js
const result = ZcyZooTeam('Zero');
const value = 'ZooTeam';
if(result !== value) {
  throw Error(`ZcyZooTeam 結果應為${value}, 但實際結果為${result}`);
}

圖片

是否能簡化?

如果我們有多個函數需要測試,你應該不想寫許多個 if / else 代碼塊吧?所以我們要將上面的代碼塊進行優化成一個函數。

// no-jest.js
function expect(result) {
  return {
    // 用于判斷是否為期望值
    toBe(value) {
      if(result !== value) {
        throw Error(`結果應為${value}, 但實際結果為${result}`);
      }
      console.log('測試通過!');
    }
  }
}

// 執行測試
expect(ZcyZooTeam('Zero')).toBe('ZcyZooTeam');

經過上面的封裝,我們就可以只寫一行代碼進行測試了!

如何能清晰地看到我測的是哪個呢?

雖然上面的封裝只需要書寫一行代碼就可以測試了,但是我們不知道執行結果和測試用例之間的對應關系,我們需要輸出的文字來告訴我們當前是哪個測試用例執行了。

// no-jest.js
// 再封裝如下方法
function test(msg, fn) {
  try {
    fn();
    console.log(msg + '測試通過!');
  } catch (error) {
    console.log(msg + '測試未通過!' + error);
  }
}

test('測試ZcyZooTeam', () => {
  expect(ZcyZooTeam('Zero')).toBe('ZcyZooTeam')
})

成功和失敗都會進行提示,這樣我們就可以知道當前是哪個測試用例成功/失敗了。

Jest 的書寫方式也是同上,如果上面的一整套代碼了解了的話,你已經可以寫 Jest 的測試腳本了,下面將進入 Jest 的配置。

如何使用 Jest 測試框架進行自動化測試?

主流的前端自動化測試框架

Jasmine

Jasmine 優點:易于學習和使用,支持異步測試,可以在瀏覽器和 Node.js 環境中運行,可以生成易于閱讀的測試報告,可以與其他庫和框架集成。

MOCHA

MOCHA 優點:支持異步測試和 Promise ,可以在瀏覽器和 Node.js 環境中運行,可以與其他庫和框架集成,可以生成易于閱讀的測試報告,可以使用各種插件和擴展來增強其功能。

Jest

Jest 是針對模塊進行測試,單元測試對單個模塊進行測試,集成測試對多個模塊進行測試。

Jest 優點:速度快(單模塊測試時,執行過的模塊不會重復執行),API簡單,易配置,隔離性好(執行環境相對隔離,每個文件單獨隔離互不干擾),監控模式(更靈活的運行各種測試用例),適配編輯器多,Snapshot(快照),多項目運行(后臺前臺測試用例并行測試),生成可視化覆蓋率簡單,Mock 豐富。

準備工作 —— Jest 的配置

npm i jest --save-D

// 初始化 jest 的配置文件
npx jest --init

// 你將在那個環境進行測試,回車即可選擇
// 第一個是 node 環境、第二個是瀏覽器環境
? Choose the test environment that will be used for testing ? - Use arrow-keys. Return to submit.
    node
?   jsdom (browser-like)

// 是否需要 jest 生成測試覆蓋率報告
? Do you want Jest to add coverage reports? ? (y/N)

// 是否需要在測試結束后清除模擬調用
? Automatically clear mock calls and instances between every test? ? (y/N)

// 創建 jest.config.js 文件
??  Configuration file created at /Users/zcy1/Desktop/demo/auto-test-jest-demo/jest.config.js

以上方法執行結束后,會生成一個 jest.config.js 文件,里面包含了 Jest 的配置項,每個配置項都會帶有描述,在初始化的兩個配置也會體現在配置文件中。

使用 babel 轉換來使用 ES6 形式的導入和導出

// .babelrc
// 如果想用 es6 的形式導出,需要使用 babel 插件進行轉換
// @babel/core  @babel/preset-env

// 創建 .babelrc 文件
// 為了在 node 環境下使用 es6 的導出,需要使用 babel 進行轉換
{
  // 設置插件集合
  "presets": [
    // 使用當前插件,可以進行轉換
    // 數組的第二項為插件的配置項
    [
      "@babel/preset-env", {
        // 根據 node 的版本號來結合插件對代碼進行轉換
        "targets": {
          "node": "current"
        }
      }
    ]
  ]
}

配置好后需要將 package.json 中的 test 命令的 value 改為 jest --watchAll ,代表監聽所有有修改的測試文件,然后控制臺執行 npm run test 就可以執行測試用例了。

Jest 啟動時會進行如下流程

  1. npm run test
  2. jest (babel-jest) 檢測當前環境是否安裝了 babel
  3. 如果安裝了則會去 babelrc 中取配置
  4. 取到后執行代碼轉換
  5. 最后再執行轉化過的測試用例代碼

如何生成一個測試用例覆蓋率報告?

經過上面的 Jest 配置,我們就可以通過下面的 npx 命令來生成測試覆蓋率報告了。

npx jest --coverage

會生成一個名為 coverage 的文件夾,打開里面的 html 就可以看到你的覆蓋率,其中 Statements 是語句覆蓋率(每個語句是否執行),Branches 是分支覆蓋率(每個 if 塊是否執行),Functions是函數覆蓋率(每個函數是否執行),Lines 是行覆蓋率(每行是否執行),通過修改 coverageDirectory 的值可以改變測試覆蓋率生成文件夾的名字。

Jest 基礎匹配器

上面我們說過了,Jest 的用法和我們封裝的那幾個函數是一樣的,都是執行 test 函數并向函數中傳遞參數,第一個參數是你當前測試用例的描述,第二個參數是需要執行的匹配規則。

匹配器

toBe

toBe 匹配器,期待是否與匹配器中的值相等 相當于 object.is ===

// jest.test.js
test("測試", () => {
  expect(1).toBe(1);  // 通過
  const a = { name: 'Zero' };
  // 因為 a 的引用地址,和 toBe 中對象的引用地址不一致,會導致測試不通過,需要使用其他的匹配器
  expect(a).toBe({ name: 'Zero' });  // 失敗
});
toEqual

toEqual 匹配器,只會匹配對象中的內容是否相等。

// jest.test.js
test('測試對象相等', () => {
  const a = { name: 'Zero' };
  expect(a).toEqual({ name: 'Zero' });  // 斷言
})
toBeNull

toBeNull 匹配器,可以判斷變量是否為 null ,只能匹配 null。

// jest.test.js
test('測試是否為null', () => {
  const a = null;
  expect(a).toBeNull();
})
toBeUndefined

toBeUndefined 匹配器,可以判斷變量是否為 undefined ,只能匹配 undefined。

// jest.test.js
test('測試是否為undefined', () => {
  const a = undefined;
  expect(a).toBeUndefined();
})
toBeDefined

toBeDefined 匹配器,希望被測試的值是定義好的。

// jest.test.js
test('測試變量是否定義過', () => {
  const a = '';
  expect(a).toBeDefined();
})
toBeTruthy

toBeTruthy 匹配器,可以判斷變量是否為真值,會對非 bool 值進行轉換。

// jest.test.js
test('測試變量真值', () => {
  const a = '123';
  expect(a).toBeTruthy();
})
toBeFalsy

toBeFalsy 匹配器,可以判斷變量是否為假值,會對非 bool 值進行轉換。

// jest.test.js
test('測試變量假值', () => {
  const a = '';
  expect(a).toBeFalsy();
})
not修飾符

not 匹配器,可以將匹配后的結果進行取反。

// jest.test.js
test('測試變量不是假值', () => {
  const a = '1';
  expect(a).not.toBeFalsy();
})
toBeGreaterThan

toBeGreaterThan 匹配器,期望值是否大于匹配器的參數。

// jest.test.js
test('是否大于 a 的數字', () => {
  const a = 123;
  expect(a).toBeGreaterThan(1);
})
toBeLessThan

toBeLessThan 匹配器,期望值是否小于匹配器的參數。

// jest.test.js
test('是否小于 a 的數字', () => {
  const a = 0;
  expect(a).toBeLessThan(1);
})
toBeGreaterThanOrEqual

toBeGreaterThanOrEqual 匹配器,期望值是否大于或等于匹配器的參數。

// jest.test.js
test('是否大于等于 a 的數字', () => {
  // toBeLessOrEqual 匹配器,與之相反
  const a = 123;
  expect(a).toBeGreaterThanOrEqual(1);
})
toBeCloseTo

js 中,浮點數值在相加時不準確,使用 toBeCloseTo 匹配器解決,趨近于 0.3。

// jest.test.js
test('是否大于等于 a 的數字', () => {
  const a1 = 0.1;
  const a2 = 0.2;
  expect(a1 + a2).toBeCloseTo(0.3);
})
toMatch

toMatch 匹配器,匹配當前字符串中是否含有這個值,支持正則。

// jest.test.js
test('是否包含 day ', () => {
  const a = 'happy every day';
  expect(a).toMatch('day');
})
toContain

toContain 匹配器,判斷當前數組中是否包含這個元素,Set 也可以使用。

// jest.test.js
test('數組中是否包含 zoo 這個元素', () => {
  const a = ['zoo', 'ZooTeam', 'Zero'];
  expect(a).toContain('zoo');
})
toThrow

toThrow 匹配器,可以捕捉拋出的異常,參數為拋出的 error ,可以用來判斷是否為某個異常。

// jest.test.js
const error = () => {
  throw new Error('error');
}
test('是否存在異常', () => {
  expect(error).toThrow();
})

以上就是 Jest 中比較基礎的匹配器,可以結合 初始化 + 配置 + 基礎匹配器 進行書寫測試用例。

命令行操作

在運行 npm run test 命令的時候,控制臺執行測試用例成功或失敗后都會像下面的圖片一樣出現幾行提示,讓你按對應的鍵進行操作。

圖片

上面幾個命令行的意思如下:

1. f 只會跑測試未通過的用例,再次點擊 f 會取消當前模式。

我們使用一個失敗的測試用例做一下示范。

圖片

圖片

按下 f 后,Jest 只會執行剛才失敗的測試用例。

圖片

2. 只監聽已改變的文件,如果存在多個測試文件,可以開啟,會與當前 git 倉庫中的提交進行比較,需要使用 git 來監聽哪個文件修改了,也可以將 --watchAll 改為 --watch 只會運行修改的文件。

3. 根據測試用例文件的正則表達式,過濾需要執行的測試用例文件,No tests found, exiting with code 0 如果填寫不對會進行提示,并不會跑任何測試用例。

4. 根據測試用例描述的正則表達式,過濾需要執行的測試用例。5. 退出測試用例監聽。

異步測試

在正常的業務開發中,項目中不只有同步代碼,還會有請求接口的異步代碼,異步代碼的測試與同步代碼有稍許不同,我們來看一下。

編寫一個接口請求

// getData.js
export const getData = (fn) => {
  axios.get('/getData').then((res) => {
    fn(res.data);
  })
}

對異步請求進行測試

// jest.test.js
// 異步調用回調函數需要添加 done 參數,是一個函數
test('getData 返回結果為 { success: true }', (done) => {
  // 此處代碼無效,因為測試用例不會等待請求結束后的回調,測試用例執行完就直接結束了
  // getData1((data) => {
  //   expect(data).toEqual({
  //     success: true
  //   })
  // })
  
  getData1((data) => {
    expect(data).toEqual({
      success: true
    })
    // 需要在結束前調用 done 函數, Jest 會知道到 done 才會結束,才可以正確測試異步函數
    done();
  })
})

需要注意的是,如果傳入了形參 done,但是沒有使用,這個測試用例就會處于一直執行的狀態,直到執行超時。

圖片

還可以結合 promise 進行使用

// getData.js
export const getData2 = () => {
  return axios.get('http://www.dell-lee.com/react/api/demo.json')
}

// jest.test.js
test('getData 返回結果為 { success: true }', () => {
  // 使用 promise 時需要 return,在 then 中使用 done 也可以
  return getData2().then(res => {
    expect(res.data).toEqual({
      success: true
    })
  })
})

// 測試請求是否 404
test('getData 返回結果為 404', () => {
  // 由于不觸發 catch 就不會走測試校驗,所以會成功,我們需要做一下限制
  // 這行代碼限制下面的代碼中必須要執行一次 expect 方法,如果非 404 就不會走下面的 expect,則測試不會通過
  expect.assertions(1);
  // 使用 promise 時需要 return
  // 如果只想測試 404 這樣寫是有問題的,需要配合 assertions 使用
  return getData2().catch(err => {
    expect(err.toString().indexOf('404') > -1).toBe(true)
  })
})

// 另一種寫法
test('getData 返回結果為 { success: true }', () => {
  // 會返回很多數據,其中包含 data 對象
  // getData2().then((res) => console.log(res))
  // {
  //   status: 200,
  //   statusText: 'OK',
  //   headers: {},
  //   ......
  //   data: { success: true }
  // }
  // resolves 方法會將接口返回的字段全部獲取,再使用 toMatchObject 方法進行匹配大對象中是否存在 data 對象
  return expect(getData2()).resolves.toMatchObject({
    data: {
      success: true
    }
  })
})

// 還可以使用 async/await
test('getData 返回結果為 { success: true }', async () => {
  await expect(getData2()).resolves.toMatchObject({
    data: {
      success: true
    }
  })
})

鉤子函數

鉤子函數可以當作一個測試用例的生命周期來看待,有 beforeAll 、beforeEach 、afterEach 、afterAll 。

以下是一些關于鉤子函數的概念和場景:

beforeAll:在所有測試用例執行前運行

beforeEach:在每個測試用例執行前執行一次

afterEach:在每個測試用例執行后執行一次

afterAll:在所有測試用例結束后運行

有時候,需要測試一個類中的多個方法,這些方法可能會反復操作同一個對象上的屬性。如果使用同一個實例,就會相互干擾,導致測試用例無法通過。此時,需要使用不同的實例來進行測試。

Counter 類

// Counter.js
class Counter {
  constructor() {
    this.number = 0;
  }

  add() {
    this.number += 1;
  }

  minus() {
    this.number -= 1;
  }
}

export default Counter;

我們想要測試里面的 add 和 minus 方法是否正確,需要實例化一個對象進行測試。但是下面的測試用例使用的永遠都是同一個實例,第二個測試用例永遠都不會通過。因為執行了第一個測試用例,第二個測試用例的值只能是 0。

// jest.test.js
const count = new Counter();
// 使用下方兩種測試方法會互相影響,先加一后減一,結果永遠是 0
test('測試加法', () => {
  count.add();
  expect(count.number).toBe(1);
})

test('測試減法', () => {
  count.minus();
  expect(count.number).toBe(-1);
})

需要使用鉤子函數,在每次執行測試用例的時候,都讓他重新實例化一個對象

// jest.test.js
let count = null;
// 類似于生命周期
// 會在測試用例執行前運行
beforeAll(() => {
  console.log('beforeAll')
});

// 會在每個測試用例執行前執行一次,這樣就會解決上面互相影響的問題
beforeEach(() => {
  console.log('beforeEach')
  count = new Counter();
});

// 會在每個測試用例執行后執行一次
afterEach(() => {
  console.log('afterEach')
});

// 會在所有測試用例結束后運行
afterAll(() => {
  console.log('afterAll');
});

test('測試加法', () => {
  console.log('add')
  count.add();
  expect(count.number).toBe(1);
})

test('測試減法', () => {
  console.log('minus')
  count.minus();
  expect(count.number).toBe(-1);
})

分組方法 discribe

// jest.test.js
let count = null;
// describe 方法,可以將測試用例進行分組,更加好維護同類型功能的測試用例
describe('count 測試', () => {
  beforeAll(() => {
    console.log('beforeAll')
  });
  beforeEach(() => {
    console.log('beforeEach')
    count = new Counter();
  });
  afterEach(() => {
    console.log('afterEach')
  });
  afterAll(() => {
    console.log('afterAll');
  });
  
  // 將 add 類型進行分組
  describe('測試 add 類型用例', () => {
    // 在 describe 方法中,鉤子函數會按照層級嵌套進行執行,先執行外部,再執行內部,不同的 describe 互不干擾
    beforeEach(() => {
      console.log('beforeEach add');
    });
    test('測試加法', () => {
      console.log('add')
      count.add();
      expect(count.number).toBe(1);
    })
  })

  // 將 minus 類型進行分組
  describe('測試 minus 類型用例', () => {
    test('測試減法', () => {
      console.log('minus')
      count.minus();
      expect(count.number).toBe(-1);
    })
  })
})

加上 describe 方法的執行效果如下圖:

圖片

Mock

在日常開發中,當前端開發差不多后,后端接口可能還沒有提供,這個時候我們就要用 Mock 數據。而 Jest 也有 Mock 方法,用于模擬一些 JavaScript 的函數等。

我們先來一個比較簡單的 mock.fn

// mock.js
export const runFn = (fn) => {
  fn(123);
}

// mock.test.js
test('測試 runFn', () => {
  // 通過 jest 的 fn 方法創建一個模擬函數,如果不傳參數會默認生成一個函數
  // 1. 通過 func.mock 獲取想要的值
  // 2. 可以自定義返回值
  // 3. 改變內部函數的實現,模擬接口請求,不請求代碼中的接口
  const func = jest.fn( () => 456 );

  // 還可以使用 mockReturnValueOnce 方法進行控制輸出,兩種方法都使用時會覆蓋 fn 方法中的返回值,支持鏈式調用
  // 將 Once 去掉與 fn 方法一樣,多次會返回相同的值
  func.mockReturnValueOnce('zoo')

  // 返回 this 方法 mockReturnThis
  func.mockReturnThis();

  // 還可以使用 mockImplementation 方法書寫函數內部,可以在函數內部寫邏輯,與 jest.fn 方法的參數一樣,還可以填加 Once
  func.mockImplementation(() => {
    return '123';
  })

  // 執行被測函數
  runFn(func);
  runFn(func);
  
  // console.log(func.mock)
  // 因為被調用了兩次,所以長度都是 2
  // {
  //   calls: [ [123], [123] ],  // 每次的調用情況,傳遞的參數是什么
  //   instances: [ undefined, undefined ],  // 每次調用的 this 指向,被調用了幾次
  //   invocationCallOrder: [ 1, 2 ],  // 執行順序,可能會傳入同一個或多個方法中,需要記錄一下順序
  //   results: [  // mock 函數每次執行后的返回值
  //     { type: 'return', value: 456 },
  //     { type: 'return', value: 456 }
  //   ]
  // }

  // 通過 toBeCalled 判斷函數是否被調用
  expect(func).toBeCalled();

  // 判斷當前函數調用了幾次 被調用了兩次
  expect(func.mock.calls.length).toBe(2);

  // 判斷參數是什么
  expect(func.mock.calls[0]).toEqual([123]);

  // 判斷每次調用的時候參數是什么
  expect(func).toBeCalledWith(123);

  // 判斷返回值
  expect(func.mock.results[0].value).toBe('zoo');
})

Mock 高階用法

如果需要通過修改請求的方式進行測試,而不使用測試框架,我們可能需要修改請求的代碼邏輯。但是,Jest 提供了一種高級的 Mock 方法。我們只需在項目根目錄下創建一個名為 __mocks__ 的文件夾,然后在其中自定義文件內容并導出,就可以使用自己定義的 Mock 函數而不必修改請求代碼邏輯。

圖片

圖片

書寫測試用例文件,引入 __mocks__ 文件夾中的函數

// mocker.test.js
// 使用 mock 方法引用 __mocks__ 下創建的 mock.js
jest.mock("./mock");
// 執行完上面的方法,會直接尋找 __mocks__ 下的getData,而不是正常的請求文件
// 由于 mock 中沒有 getCode 方法,最好只 mock 異步函數,同步函數直接測試即可
// 可以不必須創建 __mocks__ 文件夾
import {
  getData,
} from "./mock";

// 需要使用下面的 requireActual 方法來引用非 mock 文件夾下的 getCode
const { getData } = jest.requireActual("./mock");

// 高階mock
// 此處直接使用 __mocks__ 目錄下的 mock 文件中的函數
test("測試 getData", () => {
  return getData().then((data) => {
    expect(eval(data)).toEqual("123");
  });
});

Mock-timers

在特定的業務中,需要使用到定時器,測試的時候也是需要修改代碼來測試不同時間,最主要的一點是,我們需要等時間才能看到我們的執行結果,Jest 也有關于定時器的 Mock 函數。

// mock.js
export const timer = (fn) => {
  setTimeout(() => {
    fn();
    setTimeout(() => {
      fn();
    }, 3000)
  }, 3000)
}
// mock-timers.test.js
import { timer } from './mock';

// 使用 useFakeTimers 方法告知 Jest 在下面的測試用例,如果用到了定時器異步函數的時候,都是用假的 timers 進行模擬
jest.useFakeTimers();

test('測試 timer', () => {
  const fn = jest.fn();
  timer(fn);

  // 使用 runAllTimers 方法,讓定時器立即執行,和 useFakeTimers 配合使用
  jest.runAllTimers();

  // 如果代碼中有多個定時器嵌套,只想測試最外層的定時器,則需要使用 runOnlyPendingTimers 方法
  // 這個方法會只執行當前在隊列中的函數,可以多次調用
  jest.runOnlyPendingTimers();
  jest.runOnlyPendingTimers();

  // advanceTimersByTime 方法,可以快進時間
  // 因為 timer 中,三秒后只執行了第一層,如果是六秒,則會執行兩次 fn
  jest.advanceTimersByTime(3000);
})

snapshot 快照

到這里我們已經可以測試一些代碼了,但是我們要如何捕捉執行結果和當前做對比呢?這時候就要使用快照功能了。

// snapshot.js
export const config1 = () => {
  return {
    method: 'GET',
    url: '/api',
    time: new Date()
  }
}

export const config2 = () => {
  return {
    method: 'GET',
    url: '/api',
    time: new Date().getTime()
  }
}
// snapshot.test.js
import { config1, config2 } from "./snapshot";

test('測試 config1 返回值', () => {
  // 但如果每次函數修改的時候,當前測試用例也要不斷地修改
  // expect(config()).toEqual({
  //   method: 'GET',
  //   url: '/api'
  // });

  // 需要使用快照匹配 toMatchSnapshot 方法
  // 此方法會生成一個 __snapshots__ 目錄,下面的文件中,第一次執行中 config 生成的結果會存到快照文件中
  // 快照會根據 test 方法中的描述生成一個映射關系
  // 修改后的 config 的執行結果與快照中的結果不同時會報錯,需要更新快照
  // 如果 config 中有的值是每次運行都會變化的,那么每次快照都不會與當前執行相同,除非執行后再更新快照
  // 需要將在 toMatchSnapshot 方法中傳遞一個參數,設置一下 time 為任意格式的 Date 類型
  expect(config1()).toMatchSnapshot({
    time: expect.any(Date)
  });
})

test('測試 config2 返回值', () => {
  expect(config2()).toMatchSnapshot({
    time: expect.any(Number)
  });
})

行內快照生成

// snapshot.test.js
// 需要安裝 prettier
test("測試 config2 返回值", () => {
  // toMatchInlineSnapshot 方法,將執行快照放到行內中,會放到 toMatchInlineSnapshot 方法中
  expect(config2()).toMatchInlineSnapshot(
    {
      time: expect.any(Number)
    },
    `
    Object {
      "method": "GET",
      "time": Any<Number>,
      "url": "/api",
    }
  `
  );
});

對 dom 節點測試

Jest 內部自己模擬了一套 jsDom ,可以在 node 的環境下執行需要瀏覽器環境 dom 的測試用例。

// dom.js
import $ from 'jquery';
const addDiv = () => {
  // jQuery
  $('body').append('<div/>');
}

export default addDiv;

// dom.test.js
import addDiv from './dom';
import $ from 'jquery';

test('測試 addDiv', () => {
  addDiv();
  addDiv();
  console.log($('body').find('div').length);

  // 測試dom
  expect($('body').find('div').length).toBe(2);
  expect(document.getElementsByTagName('div').length).toBe(2);
})

VSCode 插件

Jest Snippets 用于快速生成 Jest 代碼塊的工具。

Jest 能夠檢測當前文件夾中的測試用例并自動運行測試,還支持可視化操作,更新、執行以及單個執行等功能,非常方便!

常用配置解讀

module.exports = {
  // 檢測從哪個目錄開始,rootDir 代表根目錄
  roots: ["<rootDir>/src"],

  // 代碼測試覆蓋率通過分析那些文件生成的,!代表不要分析
  collectCoverageFrom: [
    // src 下所有 js jsx ts tsx 后綴的文件
    "src/**/*.{js,jsx,ts,tsx}",

    // src 下所有 .d.ts 后綴的文件
    "!src/**/*.d.ts"
  ],

  // 運行測試之前,我們額外需要準備什么
  setupFiles: ["react-app-polyfill/jsdom"],

  // 當測試環境建立好后,需要做其他事情時可以引入對應的文件
  setupFilesAfterEnv: ["<rootDir>/src/setupTests.js"],

  // 哪些文件會被認為測試文件
  testMatch: [
    // src 下的所有 __tests__ 文件夾中的所有的 js jsx ts tsx 后綴的文件都會被認為是測試文件
    "<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",

    // scr 下的所有一 .test/spec.js/jsx/ts/tsx 后綴的文件都會被認為是測試文件
    "<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}",
  ],

  // 測試運行的環境,會模擬 dom
  testEnvironment: "jsdom",

  // 測試文件中引用一下后綴結尾的文件會使用對應的處理方式
  transform: {
    // 如果引用的是 js jsx mjs cjs ts tsx 后綴的文件會使用 /config/jest/babelTransform.js 文件進行處理
    "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "<rootDir>/config/jest/babelTransform.js",

    // 如果引用的是 css 后綴的文件,會使用 /config/jest/cssTransform.js 文件處理
    "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",

    // 不是以 js jsx mjs cjs ts tsx css json 這些為后綴的文件會使用 /config/jest/fileTransform.js 文件進行處理
    "^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)":
      "<rootDir>/config/jest/fileTransform.js",
  },

  // 忽略 transform 配置轉化的文件
  transformIgnorePatterns: [
    // node_modules 目錄下的 js jsx mjs cjs ts tsx 后綴的文件都不需要轉化
    "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$",

    // .module.css/sass/scss 后綴的文件都不需要轉化
    "^.+\\.module\\.(css|sass|scss)$",
  ],

  // 自動化測試時,應用的模塊應該從哪里尋找,默認是在 node_modules
  modulePaths: [],

  // 模塊名字使用哪種工具進行映射
  moduleNameMapper: {
    // 針對于 native 移動端
    // "^react-native$": "react-native-web",

    // 將 .module.css/sass/scss 模塊使用 identity-obj-proxy 工具進行轉化
    "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy",
  },

  // 引入模塊時,進行自動查找模塊類型,逐個匹配
  moduleFileExtensions: [
    "web.js",
    "js",
    "web.ts",
    "ts",
    "web.tsx",
    "tsx",
    "json",
    "web.jsx",
    "jsx",
    "node",
  ],

  // 監聽插件
  watchPlugins: [
    "jest-watch-typeahead/filename",
    "jest-watch-typeahead/testname",
  ],

  // 重置 mock
  resetMocks: true,
};

小結

在實際業務應用中,我們建議對可復用的組件、工具函數、工具類等一些無副作用,可預知結果的代碼來進行單元測試。在前期開發過程中的投入會大于沒有單元測試的投入,因為要寫一些測試用例,還要執行測試用例,優化代碼等。但是在長久迭代中,這種方法會比沒有進行單元測試的模塊更加穩定。

代碼地址

  1. 前置 demo :https://github.com/Jadony/Jest-demo
  2. Jest 簡單配置:https://github.com/Jadony/jest-config
  3. Jest 匹配器:https://github.com/Jadony/jest-matchers
  4. 異步代碼測試:https://github.com/Jadony/jest-async
  5. Jest 鉤子函數:https://github.com/Jadony/jest-hook
  6. Jest 的 mock 函數:https://github.com/Jadony/jest-mock
  7. Jest 的快照:https://github.com/Jadony/jest-snapshot
  8. Jest 對 Dom 節點的測試:https://github.com/Jadony/jest-dom

參考文檔

  • 《前端要學的測試課 從Jest入門到TDD/BDD雙實戰》(https://coding.imooc.com/class/chapter/372.html#Anchor)
責任編輯:武曉燕 來源: 政采云技術
相關推薦

2021-06-26 07:40:21

前端自動化測試Jest

2021-06-30 19:48:21

前端自動化測試Vue 應用

2021-06-25 10:57:30

前端自動化測試開發

2009-08-19 09:00:48

單元測試框架自動化測試

2017-12-24 21:00:10

自動化測試測試框架敏捷

2022-09-14 10:00:12

前端自動化測試

2011-04-18 12:52:37

自動化測試功能測試軟件測試

2023-10-12 07:40:54

Minium自動化框架

2020-09-14 07:00:00

測試自動化框架

2020-04-28 09:00:00

測試測試自動化

2016-09-26 16:42:19

JavaScript前端單元測試

2011-06-03 17:06:09

自動化測試

2019-04-18 09:00:00

Java自動化測試框架

2011-12-23 17:09:57

自動化測試

2023-11-08 13:18:00

JestJavaScript框架

2012-12-24 22:54:31

2014-04-16 14:15:01

QCon2014

2010-09-08 15:25:09

自動化測試技術網站鏈接測試

2011-08-16 15:36:47

iPhone應用測試

2021-07-02 17:22:50

前端TDDBDD
點贊
收藏

51CTO技術棧公眾號

亚洲区免费影片| 久久av老司机精品网站导航| 欧美日韩激情一区| 天天干天天色天天爽| 中文字幕天堂在线| 97色伦图片97综合影院| 日韩欧美高清dvd碟片| 亚洲小视频在线播放| 五月天丁香视频| 在线一区免费观看| 色偷偷偷亚洲综合网另类| 在线中文字日产幕| 高清av不卡| 亚洲精选一二三| 欧美成人在线免费观看| 国产超碰人人爽人人做人人爱| 国产最新精品| 精品国产第一区二区三区观看体验| 麻豆av免费在线| 污视频网站在线免费| 国产麻豆精品久久一二三| 日本精品久久中文字幕佐佐木| 国产又黄又粗又猛又爽的| 老牛精品亚洲成av人片| 欧美一区在线视频| 亚洲激情在线观看视频| 波多野结衣久久| 中文字幕亚洲欧美在线不卡| 国产一区福利视频| 91精品国产综合久| 日韩电影免费在线看| 日韩中文第一页| a天堂中文字幕| 黄色欧美网站| 欧美一区二区三区在线视频 | 九九久久婷婷| 亚洲国产精品久久| 午夜影院福利社| 成人日韩视频| 欧美日韩在线电影| 乱子伦视频在线看| 波多野一区二区| 亚洲一区二区三区四区不卡| 天天爱天天做天天操| аⅴ资源新版在线天堂| 久久久久久日产精品| 国产精品香蕉视屏| 成人免费视频国产| 国产91在线观看丝袜| 91夜夜未满十八勿入爽爽影院| 日本中文字幕第一页| 亚洲欧美成人综合| 热99精品里视频精品| 毛片在线免费视频| 久久精品综合| 国产成人精品在线| 久操视频在线免费观看| 免费日韩av片| 国产脚交av在线一区二区| 秋霞av一区二区三区| 丝袜美腿成人在线| 国产精品视频自在线| 在线观看亚洲一区二区| 欧美视频日韩| 国语自产精品视频在免费| 国产午夜精品无码| 国产精品五区| 久久久噜久噜久久综合| 国产69精品久久久久久久久久| 国产精品尤物| 国产精品第一视频| 在线观看国产小视频| 九九国产精品视频| 电影午夜精品一区二区三区| 图片区 小说区 区 亚洲五月| 91蝌蚪porny| 欧美极品日韩| 高清日韩av电影| 亚洲欧洲另类国产综合| 婷婷视频在线播放| 日本无删减在线| 日韩欧美999| 丰满少妇在线观看| 精品视频一区二区三区在线观看| 制服丝袜亚洲播放| 少妇欧美激情一区二区三区| 欧美巨大xxxx| 亚洲小视频在线| 日韩高清dvd碟片| 在线看片日韩| 国产精品爽爽爽爽爽爽在线观看| 国产高清第一页| 26uuu亚洲综合色欧美| 天堂av一区二区| 免费网站在线观看人| 色综合色综合色综合色综合色综合 | 欧美男男同志| 中文字幕一区二区在线观看| av高清在线免费观看| 97成人超碰| 亚洲白拍色综合图区| 三上悠亚影音先锋| 久久精品亚洲人成影院| 欧美一区二区三区四区在线| 超碰在线免费97| 成人综合婷婷国产精品久久| 日本欧美精品久久久| aaa大片在线观看| 色婷婷精品大在线视频| 日韩一级免费片| 日韩精品免费一区二区夜夜嗨| 丝袜一区二区三区| 欧美 日韩 精品| 国产成人啪免费观看软件| 欧洲精品码一区二区三区免费看| 2021国产在线| 欧美少妇性性性| 女人被狂躁c到高潮| 在线电影一区二区| 国产精品一区二区三区久久| 免费的黄色av| 亚洲欧洲制服丝袜| 日本熟妇人妻中出| 久久综合五月婷婷| 久久久噜噜噜久噜久久| 国产91视频在线| 国产精品久久久久久久蜜臀| 日本黄色三级大片| 电影一区二区在线观看| 久久中文字幕在线| 无码人妻精品一区二区三区9厂| 久久99精品久久久久久国产越南 | 日本wwwwwww| 欧美在线不卡| 国产啪精品视频| 男同在线观看| 日韩欧美精品在线观看| 一级欧美一级日韩片| 国产精品啊v在线| 91免费版黄色| 天堂地址在线www| 欧美无砖专区一中文字| 亚洲国产天堂av| 老妇喷水一区二区三区| 欧美精品尤物在线| 爱啪啪综合导航| 精品一区精品二区| 国产无码精品视频| 成av人片一区二区| av网站大全免费| 亚洲成人偷拍| 欧美激情aaaa| 99热这里只有精品3| 亚洲美女在线一区| 成人在线观看一区二区| 精品99视频| 久久99国产精品| 美女100%一区| 中日韩美女免费视频网址在线观看 | 国产精品久久久久久久7电影 | 中文字幕日韩欧美精品在线观看| 日日噜噜噜噜人人爽亚洲精品| 91热门视频在线观看| 久久精品xxx| 国产精品一区二区美女视频免费看 | 日本少妇毛茸茸| 亚洲大胆在线| 久久综合一区二区三区| 亚洲www.| 按摩亚洲人久久| 一级全黄少妇性色生活片| 亚洲欧美激情视频在线观看一区二区三区| 性生活免费在线观看| 99精品美女| 99在线影院| 国产在线高清视频| 精品美女在线观看| 亚洲视频 欧美视频| 欧美激情中文不卡| 在线免费黄色网| 在线欧美三区| 欧美激情第六页| 国产精品毛片无码| 97视频在线观看免费高清完整版在线观看| 手机av在线免费观看| 一本色道**综合亚洲精品蜜桃冫| 国产chinese中国hdxxxx| 日韩精品亚洲专区| 在线视频欧美一区| 久久久精品国产**网站| 国产精品福利无圣光在线一区| 国产成人l区| 日韩av一区在线| 国产精品区在线观看| 五月综合激情日本mⅴ| 天天舔天天操天天干| 国产一区在线观看视频| aa在线免费观看| 欧美va久久久噜噜噜久久| 成人免费网站在线看| 神马久久午夜| 欧美另类第一页| 国产鲁鲁视频在线观看免费| 精品乱人伦小说| 免费看一级视频| 亚洲国产另类av| 二区三区四区视频| 成人av片在线观看| 激情久久综合网| 日产国产欧美视频一区精品| www.亚洲成人网| 色综合久久网| 久久国产精品一区二区三区| 精品三级久久久| 国产日韩欧美电影在线观看| 女人让男人操自己视频在线观看| 美日韩精品免费视频| 桃花色综合影院| 欧美成人乱码一区二区三区| 精品久久免费视频| 亚洲免费色视频| 美国黑人一级大黄| 国产91精品免费| 亚洲欧美激情网| 国产日韩高清一区二区三区在线| 色中文字幕在线观看| 久操成人av| 免费99视频| 外国成人在线视频| 国产日韩欧美一区二区| 亚洲性视频在线| 91免费观看网站| 成人污污视频| 欧美在线视频免费| 17videosex性欧美| 欧美黑人性视频| 自拍亚洲图区| 久久福利视频网| 免费的黄网站在线观看| 中文字幕欧美亚洲| 欧美日韩国产中文字幕在线| 亚洲男人天堂2023| 三级黄视频在线观看| 日韩va亚洲va欧洲va国产| 好吊妞视频一区二区三区| 精品成人av一区| 尤物视频在线观看国产| 亚洲成人777| 国产乡下妇女做爰毛片| 亚洲精品一二三| 中文字幕91视频| 亚洲人一二三区| 午夜写真片福利电影网| 国产精品情趣视频| 国产精品精品软件男同| 亚洲另类一区二区| 国产一级视频在线观看| 亚洲综合色噜噜狠狠| 日本学生初尝黑人巨免费视频| 五月综合激情婷婷六月色窝| 天天操中文字幕| 天天做天天摸天天爽国产一区| 国产手机在线视频| 亚洲韩国精品一区| 欧美日韩精品区| 在线观看亚洲精品视频| 在线免费观看高清视频| 日韩一区和二区| 午夜一区在线观看| 在线观看精品国产视频| 久久精品国产亚洲a∨麻豆| 丝袜亚洲欧美日韩综合| 人人超在线公开视频| 57pao成人永久免费视频| 久久精品女人天堂av免费观看| 国产精品com| 精品123区| 5g国产欧美日韩视频| 国内精品麻豆美女在线播放视频 | 麻豆av免费看| 91丨porny丨中文| 国产又黄又粗又猛又爽的| 亚洲一区二区三区四区在线免费观看 | 亚洲午夜精品一区二区三区他趣| 青青草免费观看视频| 欧美日韩你懂得| 欧美视频在线观看一区二区三区| 精品亚洲一区二区三区在线播放| av二区在线| 国内精品伊人久久| 国产第一亚洲| 国产高清精品一区| 成人毛片在线| 日本美女爱爱视频| 精品动漫3d一区二区三区免费版 | 日韩中文字幕av在线| 国产精品jizz在线观看美国| 免费观看成人在线视频| 国产成人亚洲精品青草天美| b站大片免费直播| 国产精品日韩精品欧美在线| 国产亚洲精品女人久久久久久| 在线观看成人小视频| 国产18精品乱码免费看| 色噜噜狠狠狠综合曰曰曰88av| 成人av福利| 国产精品久久久久久久久| 久久悠悠精品综合网| 最近免费观看高清韩国日本大全| 亚洲美洲欧洲综合国产一区| 久久久久久久久久久久久久久国产| 97se亚洲国产综合在线| 一级黄色毛毛片| 欧美日韩在线视频一区| 亚洲爱爱综合网| 国产亚洲欧美视频| 在线能看的av网址| 高清av免费一区中文字幕| 国产精品成人a在线观看| 无码内射中文字幕岛国片| 国内精品国产三级国产a久久 | 青青草成人影院| 国产毛片久久久久久国产毛片| 久久精品国产精品青草| 国产精品亚洲无码| 亚洲一区在线观看免费观看电影高清| 中文字幕一区二区人妻痴汉电车| 日韩电影网在线| 538在线精品| 国产精品v欧美精品∨日韩| 911久久香蕉国产线看观看| 亚洲污视频在线观看| 国产视频视频一区| 中文字幕在线播| 日韩国产精品亚洲а∨天堂免| 暖暖在线中文免费日本| 亚洲自拍av在线| 欧美在线网站| 999精品网站| 国产亚洲欧美中文| 中文字幕一区二区人妻视频| 国产丝袜精品视频| 高清不卡av| 神马影院一区二区三区| 日韩电影一区二区三区四区| 久久婷婷五月综合| 色婷婷久久久综合中文字幕 | av欧美精品.com| 国产精品自拍视频一区| 337p日本欧洲亚洲大胆色噜噜| 538在线视频| 国产福利久久精品| 国产亚洲永久域名| 成人影视免费观看| 日本精品视频一区二区三区| 国产福利在线视频| 国产精品久久久久久久久久新婚 | 在线免费一区二区| 亚洲最大中文字幕| avtt久久| 91午夜在线观看| 国产精品一区不卡| 欧美福利视频一区二区| 国产丝袜精品第一页| 国产91欧美| 午夜探花在线观看| 丁香婷婷综合网| 日本少妇激情舌吻| 亚洲精品国精品久久99热 | 狠狠久久亚洲欧美专区| 蜜桃视频久久一区免费观看入口| 高清欧美性猛交xxxx黑人猛交| 日韩av不卡一区| 国产精品无码av无码| 国产精品色呦呦| 亚洲精品免费在线观看视频| 午夜精品久久久久久99热| 国产欧美日韩在线观看视频| 狠狠操精品视频| 亚洲男人的天堂在线aⅴ视频| 亚洲国产精品无码久久| 日本精品在线视频| 99久久综合| 色婷婷狠狠18禁久久| 欧美性xxxx18| 男人影院在线观看| 国产不卡一区二区在线观看 | 国产日韩欧美影视| 在线 亚洲欧美在线综合一区| 欧美多人猛交狂配| 91精品国产一区二区人妖| 2018av在线| 亚洲啪啪av| 国产精品亚洲一区二区三区在线 | 国产精品一区在线播放| 久久午夜影视| 国产亚洲精久久久久久无码77777| 亚洲一级片在线看| 国产精品久久久网站| 亚洲一区日韩精品|