Trong phần thứ hai của series All things about unit test with Jest, mình sẽ hướng dẫn các bạn cài đặt và viết unit test step by step
.
Ngoài ra mình sẽ đề cập thêm một vài thuật ngữ
hay được nhắc đến trong unit test
, nhằm giúp mọi người hiểu rõ hơn về cách gọi informal hay formal của people in the world 🤜🏻
Intro
Trước tiên mình sẽ nhắc đến cách sử dụng từ formal hay informal. Ở phần đầu tiên mình đã đề cập đến Test Double
nhằm giúp việc kiểm thử
trở nên dễ dàng cũng như kiểm soát nó theo hành vi
nhất định nào đó.
- Từ
Mock
là 1 từ đồng nghĩa với từTest Double
mỗi khi nhắc vềunit test
, từmock
là cách nói informal, cònTest Double
chính là cách nói formal - Vậy tại sao lại như vậy nhỉ?? 🙄 Tại sao chúng ta có đến 2 từ? Tại sao chúng ta không dùng
Test Double
thay vìMock
?
Hãy nhắc một chút về lịch sử:
- Rất lâu về trước đây, một số người đã
định nghĩa
vàgiới thiệu
về thuật ngữMock Object
. Sau đó càng có nhiều người biết đến và sử dụng nó thường xuyên hơn - Có những người chưa từng đọc hay nghe về thuật ngữ này, khi bắt đầu sử dụng nó, họ đã chế thuật ngữ này thành động từ : )
- Ví dụ: We’ve got a lot of mocking to do.
Bạn thử nghĩ xem khi nhắc tới việc làm1 unit test
, chúng ta sẽ thường nói rằng:Let's mock that
thay vì nói:Let's make a test double for that
.
Hơn nữa khi dùng từMock
thì mọi thứ nghe có vẻ nhanh và dễ dàng hơn.
- Ví dụ: We’ve got a lot of mocking to do.
Và đó là lí do vì sao mà Mock
hay được dùng hơn từ Test Double
, mình muốn nhắc đến nó vì mong rằng sẽ không ai bị confuse
như mình khi tìm hiểu về nó 😄
Nếu bạn chưa có kiến thức gì về unit test, mình recommend bạn nên đọc phần 1 trước.
Thông tin package
- "body-parser": "^1.20.2",
- "express": "^4.19.2",
- "mysql2": "^3.9.5",
- "sequelize": "^6.37.3"
- "jest": "^29.7.0"
Các bạn có thể tải toàn bộ source code của phần này ở đây
Cài đặt
Đầu tiên các bạn hãy tạo thư mục để bắt đầu viết unit test
đầu tiên nhé
Các bạn có thể chọn thư mục nào bạn muốn để tạo, ở đây mình sẽ dùng terminal
và chọn thư mục Documents
trên máy mình để làm thư mục.
Nếu ai chưa từng dùng các dòng lệnh này thì nó chỉ đơn giản là mình tạo thư mục mới
và di chuyển vào nó.
$ pwd
/Users/tienminh/Documents
$ mkdir testing_with_jest
$ cd testing_with_jest
Oke, sau đó mình sẽ dùng lệnh npm init -y
và đồng thời tải các dependency cần thiết cho dự án lẫn việc tạo ra file config cho jest
$ npm init -y
$ npm i jest@29.7.0 --save-dev
$ npm i body-parser@1.20.2 express@4.19.2 mysql2@3.9.5 sequelize@6.37.3
$ npm init jest@29.7.0
Sau khi gõ dòng lệnh npm init jest@29.7.0
, các bạn lưu ý chọn các option như mình nhé, các bạn để ý bên phải sẽ là lựa chọn của mình:
Sau đó các bạn hãy vào package.json
và thêm vào phần scripts
này cho mình:
Bây giờ các bạn hãy tạo ra cấu trúc thư mục như sau:
Oke, đó là tất cả những gì chúng ta cần setup
cho project này. Dễ phải không 😆 giờ chúng mình sẽ cùng nhau viết file unit test
đầu tiên.
Viết file unit test đầu tiên
1. Một vài điều cần chú ý
Để tạo file test
sử dụng với jest thì chúng ta sẽ tạo theo tên như sau: <tên_file>.spec.js hoặc <tên_file>.test.js.
NOTE:
Jest đã được cấu hình mặc định là sẽ tự động tìm các file có chứa suffix .spec hoặc .test
Mình sẽ viết unit test cho isEven.js
// src/isEven.js
const isEven = (number) => { return number % 2 === 0;
} module.exports = isEven;
Ta thấy function isEven
nhận vào 1 đối số là 1 số
, và sẽ trả về true
nếu là số chẵn
, hoặc false
nếu là số lẻ
. Vậy nên ta sẽ có 2 test case từ điều này.
- Nếu đối số là chẵn, trả về
true
. - Nếu đối số là lẻ, trả về
false
2. Công cụ sử dụng
Để tạo ra 1 test case
trong Jest, chúng ta sẽ sử dụng test() function. Hàm này sẽ lấy 2 đối số đầu vào, đối số thứ nhất là tên của test case
, đối số thứ hai là 1 handler function nhằm thực hiện các assertions
.
NOTE:
Hàm test() function còn có 1 alias là it() function. Chọn sử dụng cái nào là tuỳ thuộc vào sở thích và bạn.
Trong phạm vi bài viết này, mình sẽ chủ yếu dùng test(), còn ở các phần sau mình sẽ dùng it().
Test dựa trên các assertions(các bạn chưa biết là gì thì có thể hiểu theo nghĩa đen của nó là khẳng định), và chính xác là như vậy, khi ta cần khẳng định một điều gì đó với mỗi test case.
assertions = expectation + matchers
Một expectation
thì được tạo ra bởi expect() function. Nó trả về 1 object
chứa các matcher method, và khi đó chúng ta có thể thực hiện assert điều gì đó mà chúng ta expected về tested value.
3. Thực hành
Đầu tiên mình sẽ tạo file isEven.spec.js
trong thư mục src
:
// đang ở thư mục /src nhé
$ touch isEven.spec.js
Sau đó các bạn tiến hành viết mã như sau:
// isEven.spec.js
const isEven = require('./isEven.js'); test("isEven passes for even number", () => { expect(isEven(2)).toBe(true);
}) test("isEven passes for odd number", () => { expect(isEven(3)).toBe(false);
})
Mình sẽ giải thích đoạn mã trên:
- Ở đây chúng ta sẽ có 2 test case như đã nói từ trước.
- Ở test case đầu tiên, chúng ta expect isEven() function sẽ trả về true (matcher ở đây là toBe())
- Ở test case thứ hai, chúng ta expect isEven() function sẽ trả về false (vì truyền số lẻ vào)
- Ngoài ra, các bạn có thể tham khảo expect và matchers tại đây
Sau đó chúng mình sẽ gõ lệnh này trong terminal
: (ở đây chúng ta dùng npm run test
vì chúng ta đã config scripts test
trong file package.json
rồi)
// lệnh này sẽ run toàn bộ các file test của các bạn.
// các bạn có thể tuỳ chỉnh muốn chỉ chạy 1 file test nào đó thì chạy lệnh này: // npm run test <đường dẫn đến file test>
$ npm run test
Boom!!! 😃
Chúc mừng bạn đã viết thành công unit test
đầu tiên.
NOTE:
- Vì chúng ta đã có config ban đầu cho file jest.config.js rằng sẽ tự động coverage nên khi run test, nó sẽ tự động tạo folder coverage trong thư mục dự án cũng như hiển thị coverage lên terminal.
Sử dụng fixtures trong Jest
Để sử dụng fixtures trong Jest, chúng ta có thể sử dụng test.each()
function. Nó dùng để viết test gọn hơn
khi mà chúng ta có các giá trị cố định trong 1 array nào đó
(hiểu đơn giản thì các bạn duyệt mảng rồi test vậy.
Các giá trị này thì được truyền vào như 1 parameter
trong hanlder function
của test.
// isEven.spec.js
const isEven = require('./isEven.js'); const integerEvenNumbers = [2, 100, 6, 20];
const integerOddNumbers = [1, 3, 101, 2001, 23]; test.each(integerEvenNumbers)( "integerEvenNumbers passes for integer value %i", (evenNumber) => expect(isEven(evenNumber)).toBe(true) ) test.each(integerOddNumbers)( "integerEvenNumbers passes for integer value %i", (oddNumber) => expect(isEven(oddNumber)).toBe(false) )
Sau đó chúng ta gõ lệnh:
$ npm run test isEven.spec.js
Kết quả: Giải thích:
- Ở đoạn code trên, bạn thấy mình có sử dụng
%i
thì đó chính là vì giá trị củafixture
(ở đây là các số) thì có thểinject
thông quadescription
vềtest
(nó khá giống vớiC
khi chúng ta dùngprintf
).
Nhóm các test case lại thành test suite
Sau khi đã quen với unit test
đơn giản, thì chúng ta lại có nhu cầu muốn gom nhóm
các test case có liên quan đến nhau lại, nhằm để sau này đọc lại, chúng ta có thể dễ dàng tìm kiếm
cũng như tổ chức test case
khoa học hơn.
Jest thấu hiểu mong muốn đó, và nó cung cấp cho ta describe function(), nhằm tạo ra các test suite
. Nó cũng giống test function(), nó yêu cầu 2 đối số đầu vào, đối số thứ nhất
là tên test suite
, đối số thứ hai
là handler function
.
Vậy test suite là gì?
Test suite
là 1 tập hợp cáctest cases
được nhóm lại.- Mục tiêu của nó là để tổ chức
tests
theo hành vi chung hoặc 1 chức năng chung nào đó.
// isEven.spec.js
const isEven = require('./isEven.js');
describe("isEven", () => { const integerEvenNumbers = [2, 100, 6, 20]; it.each(integerEvenNumbers)( "integerEvenNumbers passes for integer value %i", (evenNumber) => expect(isEven(evenNumber)).toBe(true) ) const integerOddNumbers = [1, 3, 101, 2001, 23]; it.each(integerOddNumbers)( "integerEvenNumbers passes for integer value %i", (oddNumber) => expect(isEven(oddNumber)).toBe(false) )
})
Và run nó:
$ npm run test isEven.spec.js
NOTE:
- Các bạn để ý thấy rằng, description isEven được gom nhóm lại, và nó là 1 cấp cha cho các
test case
bên trong nó.
Độ phủ của unit test (coverage)
Để lấy được report coverage
với Jest, chúng ta sẽ sử dụng thêm --coverage
flag .
Chúng ta có thể gõ lệnh này vào terminal
:
$ npm run test --coverage
NOTE:
- Nếu phạm vi kiểm tra là 100% thì nghĩa là mọi dòng mã trong 1 unit đều đã được gọi bởi test
- Nhưng phạm vi 100% không có nghĩa là chúng ta đã cover hết toàn bộ trường hợp, mà chỉ toàn bộ dòng mã.
Kết luận
Vậy là chúng ta đã cùng nhau đi qua cách viết 1 unit test đơn giản nhất cũng như setup
cho project. Ngoài ra chúng ta còn tìm hiểu thêm được về cách gom các test case có liên quan đến nhau thành test suite.
Jest có hỗ trợ chúng ta để có thể coverage unit test của chúng ta, giúp cho chúng ta phần nào biết được chúng ta đã kiểm thử được bao nhiêu phần trăm và ở đâu còn thiếu, từ đó giúp phần mềm của chúng ta trở nên tin cậy hơn.
Ở các phần sau mình và các bạn sẽ cùng dive deep into
việc viết test trong các dự án.