最近在作一個採用 TypeScript
語言編寫的項目,測試庫選擇了 Jest
。我跟着 Jest
文檔完成了入門教程後依然不知道從何開始,主要是有如下幾個問題:css
jest
;TypeScript
項目須要有哪些特殊的配置。帶着這些問題,我前往 Github
上尋找了 5 個用 TypeScript
編寫而且使用 Jest
做爲測試框架的熱門項目進行研究,這 5 個項目分別是:html
其中,我認爲 Vuetify
最有參考價值,因此本文會以 Vuetify
爲例詳細地分析它的測試入口和配置,其餘的幾個庫若是有和 Vuetify
不一樣的地方也會指出來,從而有一個更具全局觀的認識。vue
也但願你們在閱讀本文以後,可以組織讓本身滿意的測試代碼結構,也可以看懂 Jest
大多數配置的含義。node
另外,本文還涉及如下庫的一些內容,也但願能喚起你們的一些思考:git
首先須要找到自動化測試是從哪裏開始的,這裏的技巧通常是看項目根目錄下的 package.json
中有沒有相關的腳本。很幸運,在 Vuetify
項目根目錄下的 package.json
中,發現了這段代碼:github
{ // ... "husky": { "hooks": { "pre-commit": "node scripts/warn-npm-install.js && yarn run lint && yarn lerna run test -- -- -o" } } // ... }
注意 yarn lerna run test -- -- -o
這段命令,很顯然這就是我要找的測試入口的線索。這段命令的做用是運行項目的 packages
目錄下全部包的 test
腳本,並帶上 -o
參數。正則表達式
若是你不瞭解
Yarn
和Lerna
這兩個工具,你也許會看不懂上述的命令,這裏作個簡單的解釋。vue-router若是你仔細地看過 Yarn 的文檔,你會發現
Yarn
是支持yarn <command> [--args]
的寫法的,這個寫法就是調用項目本地的命令行工具(即 node_modules/.bin/ 下的腳本)執行一段命令(command
)。上述命令lerna run test
就是command
,因此上述命令實際上就是調用項目本地的Lerna
工具來執行一行命令。npm細心的你也許會注意到上述命令行中存在兩個
--
,這又是什麼意思呢?我運行了上述命令後,會發現一個這樣的提示:jsonwarning From Yarn 1.0 onwards, scripts don't require "--" for options to be forwarded. In a future version, any explicit "--" will be forwarded as-is to the scripts.這行提示是說命令行中存在一個過期的
--
參數語法,說明有一個--
是Yarn
的老語法,新版本不用傳了,但後面咱們會講到在這種場景下不得不傳。如此一來,剩下的
--
和-o
即是Lerna
命令的參數了,因此上述命令其實是調用項目本地的Lerna
執行lerna run test -- -o
。從
Learn
的文檔可知,lerna run test -- -o
的做用是執行項目的packages
目錄下全部包中含有test
腳本的命令(不含有的包會自動跳過),而--
符號能夠將後面的參數傳遞給test
腳本,和Yarn
的--
語法一致。這裏能夠回答以前的一個問題了,即爲何必須寫
Yarn
的--
參數?咱們能夠想想,若是隻留一個--
,那麼這個--
仍是會被Yarn
識別,最終致使實際運行的命令行是lerna run test -o
,這將會報錯,因此兩個--
都得保留。還有一個思考題,就是不知道你們有沒有想過這裏爲何不直接寫
lerna run test -- -o
而要寫成yarn lerna run test -- -- -o
。我一開始也進入了這個直覺陷阱,認爲通常這樣寫不也是調用本地的Lerna
工具嗎?後來才反應過來,那只是npm scripts
的特性,只在scripts
裏面那麼寫,而這裏是husky
的配置,不能直接支持調用本地的命令行工具,須要藉助Yarn
的這個特性。因而我又去翻了翻husky
的文檔,發現裏面提到若是命令要支持調用本地命令行工具執行,還須要配置~/.huskyrc
文件,這還不如使用Yarn
的特性來得方便。
接下來即是尋找哪些包裏面有 test
腳本,幸運地是,只有 packages/vuetify
這個包包含 test
腳本,腳本對應的命令以下:
"test": "node build/run-tests.js"
test
腳本實際執行了 build/run-tests.js
文件,因而我看了下 build
目錄下的 run-tests.js
,發現它針對不一樣系統運行了不一樣的測試腳本,Windows
下運行 yarn test:win32 -o
,其餘系統運行 yarn test:unix -o
。(提示:這裏的 -o
就是以前入口處帶的 -o
參數)
經過 package.json
文件能夠知道 yarn test:win32 -o
實際上運行的是 jest -i -o
,yarn test:unix -o
實際上運行的是 jest -o
(提示:當運行 yarn <script> [...args]
的時候,運行腳本對應的命令時也會加上 args
裏的那些參數)。
那麼,帶有 -i
和 沒帶有 -i
參數有什麼區別呢?經過 Jest
文檔可知,-i
是 --runInBand
的短命名方式,-i
表明全部測試會串行地在當前進程中執行,這就可以逐步調試了,而 Jest
默認是將測試經過建立子進程的方式運行,沒法調試。想必 Windows
不經過串行方式在當前進程中執行,會遇到一些問題吧,具體是什麼問題還須要你們親身測試下告訴我哈。
而 -o
參數則表明僅對更改的文件進行測試,配合 Git
使用就很節約時間了。
至此,忽略可有可無的 -i
參數,咱們知道了 Vuetify
項目的測試入口實際就是在 packages/vuetify
下運行 jest -o
。
與 Vuetify
不一樣的是,其餘項目不多將測試時機放在 Git
鉤子上觸發,而是手動執行一個 npm script
觸發,因此不須要依賴 Yarn
的運行本地命令的功能。另外,其餘項目也都不和 Vuetify
同樣是個多個 Packages
在一塊兒的項目,因此也不須要用到 Lerna
。
Jest
的運行離不開它的配置,Jest
默認的配置文件是 jest.config.js
,或者在 package.json
中直接寫配置也行,大多數項目都是採用前一種方式,mobx
則是採用後面這種方式,這個看我的喜愛吧,我更喜歡前面的方式,不至於讓 package.json
變得很長。另外,ant-design
是經過 --config
參數來指定本身的配置文件,這是由於 ant-design
須要有不一樣的測試環境來進行測試,若是你的項目也有這個需求,就能夠自定義配置文件了。
如下是 Vuetify
項目中 packages/vuetify
的配置(提示:已經將配置中依賴的其餘配置展開),基本上搞清楚 Vuetify
的配置,你就明白大多數配置有哪些,而且都是什麼意思了:
{ // 多於一個測試文件運行時不展現每一個測試用例測試經過狀況 verbose: false, // 測試用例運行在一個相似於瀏覽器的環境裏,能夠調用瀏覽器的 API testEnvironment: 'jest-environment-jsdom-fourteen', // 以 <rootDir>/src 這個目錄作爲根目錄來搜索測試文件(模塊) roots: [ '<rootDir>/src', ], // 在測試環境準備好以後且每一個測試文件執行以前運行下述文件 setupFilesAfterEnv: [ '<rootDir>/test/index.ts', ], // 測試文件模塊之間的引用應該是本身實現了一套相似於 Node 的引用機制 // 不過本身能夠配置,下面 module 開頭的都是配置這個的,都用例子來講明 // 例如,require('./a') 語句會先找 `a.ts`,找不到找 `a.js` moduleFileExtensions: [ 'ts', 'js', ], // 例如,require('a') 語句會遞歸往上層的 node_modules 中尋找 a 模塊 moduleDirectories: [ 'node_modules', ], // 例如,require('@/a.js') 會解析成 require('<rootDir>/src/a.js') moduleNameMapper: { '^@/test$': '<rootDir>/test/index.js', '^@/test/(.*)$': '<rootDir>/test/$1', '^@/(.*)$': '<rootDir>/src/$1', '\\.(css|sass|scss)$': 'identity-obj-proxy', }, // 轉譯下列模塊爲 Jest 能識別的代碼 transform: { '\\.(styl)$': 'jest-css-modules', '\\.(sass|scss)$': 'jest-css-modules', '.*\\.(j|t)s$': 'ts-jest', }, // 收集這些文件的測試覆蓋率 collectCoverageFrom: [ 'src/**/*.{js,ts,tsx}', '!**/*.d.ts', ], // 排除 node_modules/vue-router 包之外的都被忽略 // 說明 vue-router 這個仍是要被 `ts-jest` 轉譯 transformIgnorePatterns: [ 'node_modules/(?!vue-router)', ], snapshotSerializers: [ 'jest-serializer-html', ], // 從下列文件中尋找測試文件 testMatch: [ // Default '**/test/**/*.js', '**/__tests__/**/*.spec.js', '**/__tests__/**/*.spec.ts', ], // 將 `ts-jest` 的配置注入到運行時的全局變量中 globals: { 'ts-jest': { // 是否使用 babel 配置來轉譯 babelConfig: true, // 編譯 Typescript 所依賴的配置 tsConfig: '<rootDir>/tsconfig.test.json', // 是否啓用報告診斷,這裏是不啓用 diagnostics: false, }, }, // Jest 文檔中無此配置,應該已通過時了 name: 'Vuetify', // 測試文件名旁邊顯示的標識 displayName: 'Vuetify', // 在測試環境準備好以前且每一個測試文件執行以前運行下述模塊 setupFiles: [ 'jest-canvas-mock' ] }
在進入每一項配置的解讀以前,咱們對項目中的文件分紅兩類,一類是測試文件,一類是非測試文件。所謂測試文件就是指該文件內寫的是測試代碼,非測試文件與之相反,他們之間的關係是非測試文件中的功能可能會被測試文件引用並測試。
下面進入每一項配置屬性的詳細解讀,讓你們對 Jest
運行時遵循的一些規則有所瞭解,順序按筆者認爲的重要程度排列:
字符串類型,這個就是設置 Jest
配置中 <rootDir>
模版字符串的值。默認就是 Jest
配置所在的目錄,若是你的配置寫在 package.json
文件中,那就是 package.json
文件所在的目錄,若是都沒有,則是你運行 jest
命令所在的目錄。因爲配置中常常用到,因此明白其含義是很是重要的。
這個配置就是定義 Jest
從哪些目錄裏面去搜索測試文件。默認值就是 <rootDir>
。
很重要的屬性,不知道爲何官網不最早說,我連 Jest
怎麼搜索到測試文件來跑的都不知道,那我怎麼知道該在哪寫測試文件呢?
若是說 roots
屬性規定了 Jest
搜索測試文件的範圍,那麼 testMatch
屬性就能讓 Jest
在這個範圍內精準地規定哪些文件是測試文件。若是 testMatch
定義的搜索範圍超出了 roots
定義的範圍,這些超出範圍以外的測試文件是不會執行的。
默認狀況下,Jest 搜索測試文件的原則以下:
jest.config.js
配置文件或 package.json
所在位置(通常也是 Jest
命令運行的目錄)爲根目錄;__test__
文件夾下的 js
、.jsx
、ts
、.tsx
的文件,做爲測試文件放到測試環境中運行;.test.[js|ts|jsx|tsx]
或 .spec.[js|ts|jsx|tsx]
的文件;test.[js|ts|jsx|tsx]
或 spec.[js|ts|jsx|tsx]
的文件。自定義後,搜索的文件就從根目錄按自定義的路徑和文件類型來搜尋測試文件。
瞭解這個屬性後,就知道測試文件寫在哪裏了,好比有些開源項目不配置這個屬性,直接在須要測試的源代碼文件所在的目錄中創建 __test__
文件夾,在其中寫 js
或 ts
文件,後面的系列中咱們會看到。
同時,與此相似的屬性是 testRegex
,除 Vuetify
以外的其餘四個項目基本都是使用 testRegex
,能夠自行前往 Jest 中文文檔研究。
字符串類型,表示測試用例運行的環境,內置環境能夠有兩個選擇:
另外,還能夠經過本身安裝環境包來設置環境:
<package-name>
。使用 NPM 包中的某個環境。例如,第一個 jsdom
選項實際上是 Jest
默認帶的 jsdom@11
,若是想用更高版本的 jsdom
,就得本身安裝了。Vuetify
使用的就是本身安裝的高版本 jsdom
環境,即 jest-environment-jsdom-fourteen
。文檔上還提到,若是要讓某個文件下的測試用例走單獨的環境,能夠在文件頂部加 @jest-environment <env>
註釋來實現。
另外,Jest
還支持自定義測試環境,這部分能夠自行查看官方文檔進行了解,後續有機會我也會新開一篇文章進行講解。
至於 Vuetify
爲何要用高版本 jsdom
,個人想法是越高的版本實現的瀏覽器的 API 越多,正好 Vuetify
要用到或者未來要用到更高級的 API
,因此就用了高版本的 jsdom
了。
這個屬性是設置哪些文件中的代碼是須要被相應的轉譯器轉換成 Jest
能識別的代碼,Jest
默認是能識別 JS
代碼的,其餘語言,例如 Typescript
、CSS
等都須要被轉譯。例如 Vuetify
中就用到了 ts-jest
來轉譯 Typescript
,用到了 jest-css-modules
來轉譯樣式模塊。這有點像 Webpack
的 loader
。
這個屬性是設置哪些文件不須要轉譯的。默認是 node_modules
中的模塊不須要轉譯,固然若是 node_modules
中有些模塊仍須要被轉譯,你能夠像 Vuetify
同樣設置該屬性。
接下來的三個屬性 moduleFileExtensions
、moduleDirectories
、moduleNameMapper
這些以 module
開頭的屬性都是設置模塊引用代碼的解析規則。這些規則既做用於測試文件也做用於非測試文件,默認狀況下這些規則和 Node 的模塊解析規則有點像,可是請記住測試時代碼並非運行在 Node 中,而是 testEnvironment
屬性定義的測試環境中,所以你能夠更靈活的定義模塊解析的規則。
moduleFileExtensions
屬性就規定了模塊檢索的文件類型,例如你寫了 require('./a)
,而後 moduleFileExtensions 配的是 ['ts', 'js'],那就先找 ./a.ts
,找不到就找 ./a.js
。因此你的項目用什麼語言寫的,就應該把該語言的後綴名放在最前面,這樣檢索效率也會更高。值得注意的是,這個屬性配置中必須包含 js
配置。
這個屬性對直接引用模塊名而不是路徑的方式定義了模塊搜索路徑,例如你在代碼裏面寫了 require(<module-name>)
這樣一句代碼,默認狀況下就會和 Node
同樣遞歸搜索 node_modules
目錄中是否含有這個名字的模塊。若是你配置了其餘目錄名,就會遞歸搜索那個文件夾下面的模塊了。
這個屬性是對模塊路徑映射,鍵是正則表達式,值是模塊地址,能夠用 $1
之類的符號來表示路徑中匹配的字符串。值得注意的是,值必須是完整的模塊路徑,和 Webpack
中的 alias
有所不一樣,好比以 Webpack
的思惟,我想用 @
代替項目根目錄的 src
,那麼直接寫 '@': 'src'
就好了,而這裏就不能寫成相似的 @: '<rootDir>/src'
了,得寫成 '^@/(.*)$': '<rootDir>/src/$1'
,$1
解析出來後就是個完整的模塊路徑。總的來講,Webpack
作的更像是對路徑上的部分字符串作個替換,而 Jest
則是匹配路徑後,將整個路徑用對應的值給徹底替換掉,這個思惟的轉變很重要。
可是,對於 Typescript
項目來講,可能光配置 moduleNameMapper
屬性仍是沒法解析別名路徑的,若是項目和 Vuetify
同樣,用了 ts-jest
來解析 Typescript
,那麼還得配置如下內容才能生效:
globals: { 'ts-jest': { diagnostics: false } }
這裏的緣由是,若是開啓了 diagnostics
,就會在編譯前執行診斷,診斷時會報模塊沒法找到的錯誤,從而終止編譯。
這個屬性是設置測試環境中的全局變量的,若是你在 globals
中新增了一個 name: 'vabaly'
的配置,那麼你將在代碼(包含測試文件和非測試文件)中無需聲明而直接使用 name
或者 global.name
來得到 vabaly
這個值。
這個屬性是定義在每一個測試文件運行以前,且在測試環境準備好前就會當即執行的文件或模塊。
這個屬性是定義在每一個測試文件運行以前,且在測試環境準備好後就會當即執行的文件或模塊。
不管是 setupFiles
仍是 setupFilesAfterEnv
,都會在測試文件運行第一行代碼前執行。
這個屬性是收集符合 Glob 模式匹配的文件的測試覆蓋率,這個和 collectCoverage
屬性是相關聯的,只有 collectCoverage
設爲 true
,或者命令行中帶有 --coverage
參數,這個配置纔會生效。
值得注意的是,當只設置 collectCoverage
爲 true
時,那麼就只會收集測試過程當中用到的非測試文件的測試覆蓋率。若是又設置了 collectCoverageFrom
,則只會收集 collectCoverageFrom
指定的非測試文件的測試覆蓋率,並且無論該文件有沒有在測試中被用到,都會收集,只不過該文件的測試覆蓋率是零罷了。若是你想知道你的代碼有哪些還沒被覆蓋到,設置這個屬性是十分有必要的。
布爾值,默認值是 false
。設置是否在運行期間展現每一個單獨測試用例的測試狀況(PASS or Fail)。不過不管設置什麼,都會在尾部顯示測試錯誤信息以及整體經過狀況,可看下面兩張圖體會:
值得注意的是,當 verbose
設爲 false
,且只有一個測試文件運行時,仍是會顯示出每一個單獨測試用例的測試狀況,多個測試文件運行時,就不會顯示出來了。只有 verbose
爲 true
時纔會顯示出來。
字符串類型,定義在每一個測試文件名旁邊高亮顯示的名字,方便區分不一樣包的測試,好比設置 displayName: 'TEST'
以後,終端運行測試時就會顯示成下圖這樣:
至此爲止,開頭的幾個問題都有了答案:
npm script
執行,也能夠放到 Git
鉤子上,保證每次提交都測試經過,不過要注意這二者細微的使用差異;.spec
,並放在 test
目錄下;在弄清楚 Vuetify
的測試命令和測試配置以後,接下來就到了激動人心的測試代碼欣賞時間,不過因爲本章篇幅過長以及做者精力問題,咱們下回再講!
歡迎你們 Star 筆者的 Github,另外,也歡迎你們關注筆者的公衆號,獲取最新文章的推送: