+4

Part 2: Làm thế nào để viết unit test trong Javascript với Jest

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òn Test 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ĩagiớ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àm 1 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à đó 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: Hình ảnh về việc lựa chọn các option trong lệnh trên

Sau đó các bạn hãy vào package.json và thêm vào phần scripts này cho mình: Hình ảnh thêm vào scripts

Bây giờ các bạn hãy tạo ra cấu trúc thư mục như sau: Hình ảnh cho cấu trúc dự án 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ấttên của test case, đối số thứ hai1 handler function nhằm thực hiện các assertions.

NOTE:
Hàm test() function còn có 1 aliasit() 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 expectmatchers 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!!! 😃 Hình ảnh kết quả sau khi chạy test 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ả: Hình ảnh chạy test của fixture 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ủa fixture (ở đây là các số) thì có thể inject thông qua description về test (nó khá giống với C khi chúng ta dùng printf).

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ứ haihandler function.
Vậy test suite là gì?

  • Test suite là 1 tập hợp các test 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

Hình ảnh test suite

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

Hình ảnh coverage của test

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.

Reference


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí