เป้าหมายในโครงการนี้คือการสร้างโครงการ TypeScript ที่สามารถทำสิ่งต่อไปนี้ได้ทั้งหมด:
jest
และ ts-jest
ในการทดสอบ ฉันเริ่มต้นด้วยการเริ่มต้นสิ่งนี้เป็นโปรเจ็กต์ npm
$ yarn init .
จากนั้นฉันติดตั้ง typescript
, jest
, ts-jest
และ @types/jest
เป็นการพึ่งพา:
$ yarn add -D typescript jest ts-jest @types/jest
ในขณะที่เขียนบทความนี้ นั่นหมายถึง [email protected]
, [email protected]
และ [email protected]
ต่อไป เราจะเริ่มต้นสิ่งนี้เป็นโปรเจ็กต์ TypeScript โดยใช้:
$ npx tsc --init .
ฉันต้องการให้โค้ดที่สร้างโดย TypeScript ของฉันถูกเก็บไว้ใน ./lib
และฉันต้องการให้มีการสร้างการประกาศ ดังนั้นฉันจึงกำหนดค่า outDir
ใน tsconfig.json
ให้เป็น ./lib
lib
.gitignore
ของฉันได้รับการกำหนดค่าให้เป็น:
/node_modules
/lib
...ในขณะที่ .npmignore
ของฉันเป็นเพียง:
/node_modules
ด้วยเหตุผลเดียวกัน ฉันลบค่าเริ่มต้นสำหรับ files
ใน tsconfig.json
และแทนที่ด้วย:
"exclude" : [ " node_modules " , " lib " ]
ในการเริ่มต้น ฉันสร้าง src/index.ts
ที่มีฟังก์ชันง่ายๆ:
export function sampleFunction ( x : string ) : string {
return x + x ;
}
ฉันยังเพิ่มการทดสอบ jest
แบบง่ายๆ ฉันชอบเก็บการทดสอบไว้ในตำแหน่งที่แยกจากกันโดยสิ้นเชิง ดังนั้นฉันจะเก็บการทดสอบทั้งหมดของฉันไว้ใน __tests__
ดังนั้นฉันจึงสร้างกรณีทดสอบต่อไปนี้ใน __tests__/base.spec.ts
:
import { sampleFunction } from "../src" ;
describe ( "This is a simple test" , ( ) => {
test ( "Check the sampleFunction function" , ( ) => {
expect ( sampleFunction ( "hello" ) ) . toEqual ( "hellohello" ) ;
} ) ;
} ) ;
ณ จุดนี้ ฉันอยากจะทำการทดสอบนั้น แต่ก่อนอื่นฉันต้องสร้างไฟล์ jest.config.js
สำหรับการตั้งค่า jest
ทั้งหมดของฉัน สิ่งนี้จะต้องคำนึงถึงความจริงที่ว่าฉันใช้ ts-jest
และความจริงที่ว่าการทดสอบของฉันถูกเก็บไว้ใน __tests__
ดังนั้นไฟล์ที่ได้จะมีลักษณะดังนี้:
module . exports = {
transform : {
"^.+\.tsx?$" : "ts-jest" ,
} ,
testRegex : "(/__tests__/.*|(\.|/)(test|spec))\.(jsx?|tsx?)$" ,
moduleFileExtensions : [ "ts" , "tsx" , "js" , "jsx" , "json" , "node" ] ,
} ;
ฉันเพิ่มสคริปต์ต่อไปนี้ใน package.json
:
"scripts" : {
"compile" : " tsc " ,
"test" : " jest "
}
ณ จุดนี้ ถ้าฉันรัน yarn test
ฉันจะได้สิ่งที่ฉันหวังไว้อย่างแน่นอน:
PASS __tests__/base.spec.ts
This is a simple test
✓ Check the sampleFunction function (3ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
หากต้องการเปิดใช้งานการครอบคลุมโค้ด ฉันอัปเดตไฟล์ jest.config.js
เป็น:
module . exports = {
transform : {
"^.+\.tsx?$" : "ts-jest" ,
} ,
testRegex : "(/__tests__/.*|(\.|/)(test|spec))\.(jsx?|tsx?)$" ,
moduleFileExtensions : [ "ts" , "tsx" , "js" , "jsx" , "json" , "node" ] ,
collectCoverage : true ,
} ;
ฉันยังต้องการอัปเดตไฟล์ .gitignore
และ .npmignore
ของฉันเพื่อหลีกเลี่ยงการควบคุมเวอร์ชันหรือการเผยแพร่ไดเร็กทอรี coverage
ที่สร้างโดย jest
ณ จุดนี้ ฉันจะเริ่มแนะนำโมดูลย่อยในโครงการของฉัน ดังนั้นฉันจะเพิ่มโมดูล src/core
และ src/utils
เพื่อให้สิ่งต่าง ๆ สมจริงยิ่งขึ้นเล็กน้อย จากนั้น ฉันจะส่งออกเนื้อหาของทั้งสองสิ่งนี้เพื่อให้ src/index.ts
มีลักษณะดังนี้:
export * from "./core" ;
export * from "./utils" ;
จากนั้นนำเข้าไฟล์เฉพาะที่มีประเภทและฟังก์ชันต่างๆ ขั้นแรก ฉันจะสร้างชุดประเภทที่เรียบง่ายสำหรับแสดงนิพจน์ที่เรียบง่ายอย่างยิ่งโดยมีเพียงตัวอักษรและการดำเนินการไบนารี่ +
, -
, *
และ /
จากนั้นฉันสามารถเขียนการทดสอบบางอย่างเช่นนี้:
import { evaluate , Expression } from "../src" ;
describe ( "Simple expression tests" , ( ) => {
test ( "Check literal value" , ( ) => {
expect ( evaluate ( { type : "literal" , value : 5 } ) ) . toBeCloseTo ( 5 ) ;
} ) ;
test ( "Check addition" , ( ) => {
let expr : Expression = {
type : "binary" ,
operator : "+" ,
left : {
type : "literal" ,
value : 5 ,
} ,
right : {
type : "literal" ,
value : 10 ,
} ,
} ;
expect ( evaluate ( expr ) ) . toBeCloseTo ( 15 ) ;
} ) ;
} ) ;
จนถึงตอนนี้ดีมาก แต่โปรดทราบว่าหากฉันทำการทดสอบเหล่านี้จริงๆ ฉันจะได้ผลลัพธ์เหล่านี้:
PASS __tests__/base.spec.ts
Simple expression tests
✓ Check literal value (4ms)
✓ Check addition
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.048s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 66.67 | 37.5 | 50 | 66.67 | |
src | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/core | 61.54 | 37.5 | 100 | 61.54 | |
functions.ts | 54.55 | 37.5 | 100 | 54.55 | 14,16,18,20,25 |
index.ts | 100 | 100 | 100 | 100 | |
src/utils | 66.67 | 100 | 0 | 66.67 | |
checks.ts | 50 | 100 | 0 | 50 | 2 |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|----------------|
สังเกตการขาดความครอบคลุมของโค้ด เพิ่มกรณีทดสอบอีกสองสามกรณีพร้อมกับ /* istanbul ignore ... */
ความคิดเห็นเพื่อให้ istanbul
รู้ว่าสิ่งใดที่สามารถเพิกเฉยได้อย่างปลอดภัย เราได้ไปที่:
PASS __tests__/base.spec.ts
Simple expression tests
✓ Check literal value (3ms)
✓ Check addition
✓ Check subtraction
✓ Check multiplication (1ms)
✓ Check division
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 1.353s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
src | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/core | 100 | 100 | 100 | 100 | |
functions.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/utils | 100 | 100 | 100 | 100 | |
checks.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|----------------|
ตอนนี้ ถ้าเราเปลี่ยนการทดสอบเพื่อให้ล้มเหลว เราจะได้สิ่งนี้:
● Simple expression tests › Check division
expect(received).toBeCloseTo(expected, precision)
Expected value to be close to (with 2-digit precision):
1
Received:
2
19 | test("Check division", () => {
20 | let expr = bin("/", 10, 5);
> 21 | expect(evaluate(expr)).toBeCloseTo(1);
22 | });
23 | });
24 |
at Object.<anonymous> (__tests__/base.spec.ts:21:32)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 4 passed, 5 total
Snapshots: 0 total
Time: 1.535s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
src | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/core | 100 | 100 | 100 | 100 | |
functions.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/utils | 100 | 100 | 100 | 100 | |
checks.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|----------------|
โปรดทราบว่าแทร็กสแต็กถูกต้อง มันชี้ไปที่ปัญหา ในโค้ด TypeScript
จำได้ว่าเราได้เพิ่มสคริปต์ compile
ลงใน package.json
ของเรา เราสามารถคอมไพล์โค้ดด้วย yarn compile
การทำเช่นนี้ เราจะเห็นว่าไดเร็กทอรี lib
เต็มไปด้วยไดเร็กทอรีย่อยสองไดเร็กทอรี src
และ __tests__
อย่างไรก็ตาม หากเราดูในไดเร็กทอรีเหล่านั้น เราจะพบว่าไดเร็กทอรีเหล่านั้นรวมเฉพาะโค้ด Javascript ที่สร้างขึ้นเท่านั้น ไม่รวมคำจำกัดความประเภท ในการสร้างคำจำกัดความประเภท (ไฟล์ . .d.ts
) เพื่อให้ผู้ใช้ TypeScript คนอื่นๆ ได้รับประโยชน์จากข้อมูลประเภททั้งหมดที่เราเพิ่มลงในโค้ดของเรา เราต้องตั้งค่าฟิลด์ declaration
ในไฟล์ tsconfig.json
ของเราให้เป็น true
โปรดทราบว่าเพื่อให้ผู้อื่นใช้แพ็คเกจนี้เป็นโมดูล NPM คุณต้องตั้งค่าฟิลด์ main
ใน package.json
เป็น lib/src/index.js
นอกจากนี้ เพื่อให้ผู้อื่นสามารถเข้าถึงประเภทต่างๆ ในโมดูลนี้ได้ เรายังจำเป็นต้องตั้งค่าฟิลด์ typings
ใน package.json
เป็น lib/src/index.d.ts
กล่าวอีกนัยหนึ่ง
"main" : " lib/src/index.js " ,
"typings" : " lib/src/index.d.ts " ,
หากกำหนดค่าอย่างเหมาะสม เราก็จะสามารถเปิดเซสชัน node
และนำเข้าแพ็คเกจใหม่ของเราได้:
$ node
> var me = require( " . " )
undefined
> me
{ evaluate: [Function: evaluate],
assertNever: [Function: assertNever] }
>
ตอนนี้อย่าลืมอัปเดต jest.config.js
ของคุณเพื่อรวมการตั้งค่าต่อไปนี้ ไม่เช่นนั้น jest
จะเริ่มจับคู่โค้ดในไดเร็กทอรี lib/__tests__
:
testPathIgnorePatterns: ["/lib/", "/node_modules/"],
ในที่สุดเราก็มาถึงการดีบัก ฉันใช้ Visual Studio Code ดังนั้นฉันจะสาธิตวิธีการแก้ไขข้อบกพร่องที่นั่น ข้อมูลบางส่วนนี้อาจแปลเป็น IDE อื่นๆ ได้เป็นอย่างดี
ใน VSCode เราสามารถไปที่แถบด้านข้างการดีบักได้ เริ่มแรกถัดจากปุ่ม "เล่น" จะมีคำว่า "ไม่มีการกำหนดค่า" การคลิกที่เมนูดังกล่าวจะแสดงเมนูแบบเลื่อนลงพร้อมตัวเลือก "เพิ่มการกำหนดค่า..."
มากพอๆ กับที่ฉันชอบ TypeScript การดีบักคือจุดอ่อนของมันจริงๆ ไม่ใช่ว่าคุณไม่สามารถแก้ไขข้อบกพร่องได้ แต่เป็นเพียงการทำงานที่ยากเท่านั้น หากคุณเลือก "เพิ่มการกำหนดค่า..." จากนั้นเลือก "Node.js" คุณจะเห็นการกำหนดค่าล่วงหน้าหลายอย่าง รวมถึงการกำหนดค่าสำหรับ mocha
ด้วย แต่ไม่มีอันหนึ่งสำหรับ jest
ดังนั้น คุณจะต้องสร้างไฟล์ .vscode/launch.json
ของคุณเอง โชคดีที่คำแนะนำหน้า jest
ที่คุณสร้างไฟล์ .vscode/launch.json
ที่มีลักษณะดังนี้:
{
"version" : " 0.2.0 " ,
"configurations" : [
{
"name" : " Debug Jest Tests " ,
"type" : " node " ,
"request" : " launch " ,
"runtimeArgs" : [ " --inspect-brk " , " ${workspaceRoot}/node_modules/.bin/jest " , " --runInBand " ],
"console" : " integratedTerminal " ,
"internalConsoleOptions" : " neverOpen "
}
]
}
ฉันรู้สึกประหลาดใจมากที่พบว่าไม่เพียงแต่ทำการทดสอบและรับการครอบคลุมโค้ดได้ตามปกติ แต่ยังตั้งค่าเบรกพอยต์ในการทดสอบทั้งสอง ( เช่น ใน __tests__/base.spec.ts
) รวมถึงในโค้ด ( เช่น src/core/functions.ts
) และดีบักเกอร์จะค้นหามัน
โปรดทราบว่าฉันทดสอบทั้งหมดนี้บน Node 8.x ฉันเคยเห็นปัญหาเกี่ยวกับการดีบักโดยใช้ Node 6.x ดังนั้นหากคุณประสบปัญหาดังกล่าว คุณอาจพิจารณาอัปเกรด (หรือปล่อยให้หากคุณจัดการแก้ไขได้ ให้ส่ง PR สำหรับ README นี้เพื่ออธิบายการแก้ไข)