Unit test là gì – Jest testing NodeJS cho người mới bắt đầu

Testing là gì? Làm cách nào để viết một Unit Test cho JavaScript với Jest? Tìm hiểu các kiến ​​thức cơ bản về tes JavaScript với hướng dẫn Jest cho người mới bắt đầu!


1071
1.7k shares, 1071 points

Testing có nghĩa là gì?

Trong ngành Công nghệ thông tin (CNTT) testing có nghĩa là kiểm tra code của chúng ta có đáp ứng kỳ vọng hay không . Ví dụ: một function có tên là Tổng sẽ trả về kết quả phép tính cộng một dãy số được người dùng nhập vào.

Có nhiều loại Testing và chẳng mấy chốc bạn sẽ bị choáng ngợp bởi thuật ngữ này, nhưng testing có ba loại chính :

  • Unit Testing
  • Integration Testing
  • UI Testing

Trong hướng dẫn sử dụng Jest lần này, sẽ chỉ đề cập đến Unit Testing

Jest là gì?

Jest là một thư viện JavaScript để tạo, chạy và cấu trúc test . Jest được phân phối dưới dạng NPM, bạn có thể cài đặt nó trong bất kỳ dự án JavaScript nào. Jest là một trong những thư viện test phổ biến nhất hiện nay và là lựa chọn mặc định cho việc tạo App React.

Điều đầu tiên: Làm thế nào để biết phải test cái gì?

Khi nói đến testing , ngay cả một đoạn code đơn giản cũng có thể làm tê liệt người mới bắt đầu. Câu hỏi phổ biến nhất là “Làm thế nào để biết phải test cái gì?” . 

Nếu bạn đang viết một Web-App, bạn sẽ kiểm tra mọi trang của App và mọi tương tác của người dùng. Nhưng các ứng dụng Web cũng được tạo thành từ các đoạn code như hàm(function) và mô-đun(modules) cũng cần được kiểm tra. Có hai trường hợp thông thường:

  • Thừa hưởng đoạn code mà không cần test
  • Phải thực hiện một chức năng mới 

Phải làm sao đối với cả hai trường hợp, bạn có thể tự giúp mình bằng cách nghĩ về các bài test như các bit của test xem function có return kết quả như mong muốn hay không . Đây là cách flow-test diễn ra:

  1. Chọn function để testing
  2. Nhập giá trị đầu vào ( input )
  3. Xác định những gì mong đợi là đầu ra (output)
  4. Test xem fucntion có return output mong muốn không

Flow-test đã diễn ra như vậy. Việc kiểm tra sẽ không còn đáng sợ nữa nếu bạn nghĩ theo các thuật ngữ sau: đầu vào( input ) – đầu ra dự kiến (expected output) – khẳng định kết quả(assert the result) . Trong một phút, chúng ta cũng sẽ thấy một tool tiện dụng để kiểm tra gần như chính xác những gì cần test. Và đó là Jest!

Setup the project NodeJS

Như mọi project JavaScript, bạn sẽ cần một môi trường NPM (đảm bảo đã cài đặt Node trên hệ thống của bạn). Tạo một thư mục mới và khởi tạo dự án với:

mkdir getting-started-with-jest && cd $_
npm init -y

Tiếp theo cài đặt Jest với:

npm i jest --save-dev

Chúng ta cũng hãy cấu hình NPM script để test từ dòng lệnh. Mở pack.json và cấu hình có tên “test” để chạy Jest:

  "scripts": {
    "test": "jest"
  },

Thông số kĩ thuật (Specifications)  và phát triển hướng kiểm thử (test-driven development) NodeJS

Trong hướng dẫn này, chúng tôi đã có specifications khá đơn giản từ người quản lý dự án(project manager). Clients cần một fucntions JavaScript sẽ lọc một array các objects.

Theo mặc định, Jest dự kiến( expects ) ​​sẽ tìm thấy các files test trong thư mục tên là  tests  trong thư mục project. Tạo thư mục mới sau đó:

cd getting-started-with-jest
mkdir __tests__

Tiếp theo, tạo một filei có tên là filterByTerm.spec.js bên trong tests . Tại sao có phần mở rộng “.spec.”. Đó là một quy ước mượn từ Ruby.

Và bây giờ chúng ta test nào!

Cấu trúc test và test fail lần đầu

Để tạo đoạn test Jest đầu tiên . Mở bộ lọc ByTerm.spec.js và tạo khối kiểm tra:

describe("Filter function", () => {
  // test stuff
});

Describe là phần mô tả , một phương pháp Jest để chứa một hoặc nhiều bài kiểm tra liên quan. Mỗi khi bạn bắt đầu viết test cho một chức năng, hãy viết trong describe. Như bạn có thể thấy, nó cần hai argument : một chuỗi để describe test và functions.

Tiếp theo, một chức năng khác gọi là test , đó là đoạn test thực tế:

describe("Filter function", () => {
  test("it should filter by a search (link)", () => {
    //  test thực tế
  });
});

Tại thời điểm này, chúng tôi để viết test. Hãy nhớ rằng, test là một vấn đề của  inputs, functions, và expected outputs . Trước tiên hãy xác định input, mảng của objects :

describe("Filter function", () => {
  test("it should filter by a search  (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];
  });
});

Tiếp theo, chúng tôi sẽ xác định  expected result. Theo thông số kỹ thuật, functions được test sẽ loại bỏ các objects có thuộc tính url không khớp với cụm từ tìm kiếm đã cho. Ví dụ, chúng ta có thể mong đợi array với objects, được đưa ra “link” làm thuật ngữ tìm kiếm:

describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];
    const output = [{ id: 3, url: "https://www.link3.dev" }];
  });
});

Và bây giờ chúng ta đã sẵn sàng để viết bài tes thực tế. Sẽ sử dụng  expectmatcher Jest để kiểm tra xem hàm giả tưởng (hiện tại) của chúng ta có trả về kết quả mong đợi khi được gọi không. Đây là đoạn test:

expect(filterByTerm(input, "link")).toEqual(output);

Để phân tích rõ hơn nữa đây là cách bạn sẽ gọi hàm trong code của mình:

filterByTerm(inputArr, "link");

Trong Jest, bọc chức năng bên trong expect kết hợp với matcher   (chức năng Jest để kiểm tra đầu ra) thực hiện test thực tế. Đây là đoạn test hoàn chỉnh:

describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];
    const output = [{ id: 3, url: "https://www.link3.dev" }];
    expect(filterByTerm(input, "link")).toEqual(output);
  });
});

(Để tìm hiểu thêm về matchers Jest, hãy xem tài liệu ).

Bạn gõ lệnh :

npm test

Bạn sẽ thấy đoạn test fail

 FAIL  __tests__/filterByTerm.spec.js
  Filter function
    ✕ it should filter by a search term (2ms)
  ● Filter function › it should filter by a search term (link)
    ReferenceError: filterByTerm is not defined
       9 |     const output = [{ id: 3, url: "https://www.link3.dev" }];
      10 |
    > 11 |     expect(filterByTerm(input, "link")).toEqual(output);
         |     ^
      12 |   });
      13 | });
      14 |

“ReferenceError: filterByTerm is not defined”. Hiện lỗi rất chính xác . Hãy fix trong phần tiếp theo!

Fix đoạn test fail

Điều thực sự thiếu là việc triển khai filterByTerm . Để thuận tiện, chúng tôi sẽ tạo chức năng trong cùng một file nơi test tồn tại. Trong một dự án thực tế, bạn sẽ xác định hàm trong một file khác .

Để thực hiện bài kiểm tra, chúng tôi sẽ sử dụng một hàm JavaScript là  filter  có thể lọc các phần tử từ một array. Đây là cách gọi filterByTerm :

function filterByTerm(inputArr, searchTerm) {
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(searchTerm);
  });
}

Đây là cách nó hoạt động: đối với mỗi phần tử của mảng đầu vào, chúng ta kiểm tra thuộc tính “url”, khớp nó với biểu thức chính quy với  match  . Đây là code hoàn chỉnh:

function filterByTerm(inputArr, searchTerm) {
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(searchTerm);
  });
}
describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];
    const output = [{ id: 3, url: "https://www.link3.dev" }];
    expect(filterByTerm(input, "link")).toEqual(output);
  });
});

Bây giờ test lần nữa:

npm test

và đã thành công

 PASS  __tests__/filterByTerm.spec.js
  Filter function
    ✓ it should filter by a search term (link) (4ms)
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.836s, estimated 1s

Bạn đã làm rất tốt. Nhưng chúng ta đã test xong chưa? Tất nhiên là chưa. Chúng ta còn phải test trường hợp fail . Hãy nhấn mạnh chức năng với cụm từ tìm kiếm chữ hoa:

function filterByTerm(inputArr, searchTerm) {
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(searchTerm);
  });
}
describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];
    const output = [{ id: 3, url: "https://www.link3.dev" }];
    expect(filterByTerm(input, "link")).toEqual(output);
    expect(filterByTerm(input, "LINK")).toEqual(output); // New test
  });
});

Test … và fail. Chúng ta lại tiếp tục fix

Fix bug CHỮ HOA

filterByTerm cũng nên tính đến các cụm từ tìm kiếm chữ hoa. Nói cách khác, nó sẽ trả về các đối tượng phù hợp ngay cả khi cụm từ tìm kiếm là một chuỗi chữ hoa:

filterByTerm(inputArr, "link");
filterByTerm(inputArr, "LINK");

Để test chữa hoa, chúng tôi giới thiệu kiểu test mới:

expect(filterByTerm(input, "LINK")).toEqual(output); // New test

Để thành công, chúng ta có thể điều chỉnh biểu thức được cung cấp để match :

//
    return arrayElement.url.match(searchTerm);
//

Thay vì searchTerm ngay lập tức, chúng ta có thể xây dựng một biểu thức chính quy (regular expression) không phân biệt chữ hoa chữ thường , nghĩa là một biểu thức khớp với bất kể trường hợp nào của chuỗi. Đây là cách fix:

function filterByTerm(inputArr, searchTerm) {
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

Và đây là đoạn code hoàn chỉnh:

describe("Filter function", () => {
  test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev" }
    ];
    const output = [{ id: 3, url: "https://www.link3.dev" }];
    expect(filterByTerm(input, "link")).toEqual(output);
    expect(filterByTerm(input, "LINK")).toEqual(output);
  });
});
function filterByTerm(inputArr, searchTerm) {
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

Test lại và thành công.

Bây giờ chúng ta thử viết một bài test khác với nội dung như sau

  1. Test cụm từ tìm kiếm “uRl”
  2. Test một cụm từ tìm kiếm rỗng

Trong phần tiếp theo, chúng ta sẽ thấy một điều quan trọng khác trong thử nghiệm:  code coverage (độ bao phủ mã ) .

Code coverage là gì?

Code coverage là gì ? Trước khi nói về nó, hãy điều chỉnh nhanh code. Tạo một thư mục mới bên trong project với tên là src và tạo một file có tên là filterByTerm.js nơi chúng ta sẽ đặt và xuất( export  ) hàm của chúng ta:

mkdir src && cd _$
touch filterByTerm.js

Đây là file filterByTerm.js  :

function filterByTerm(inputArr, searchTerm) {
  if (!searchTerm) throw Error("searchTerm cannot be empty");
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}
module.exports = filterByTerm;

Bây giờ hãy giả vờ tôi là một đồng nghiệp mới của bạn. Tôi không biết gì về testing và thay vì hỏi thêm , tôi đi thẳng vào hàm đó để thêm một câu lệnh if mới :

function filterByTerm(inputArr, searchTerm) {
  if (!searchTerm) throw Error("searchTerm cannot be empty");
  if (!inputArr.length) throw Error("inputArr cannot be empty"); // new line
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}
module.exports = filterByTerm;

Có một dòng mã mới bên trong filterByTerm  và dường như nó sẽ không được test. 

Và đó được gọi là  code coverage  và nó là một tool mạnh mẽ. Jest có built-in code coverage  và bạn có thể sử dụng nó theo hai cách:

  1. Bằng command line với flag “–coverage”
  2. Bằng cách config Jest trong package .json

Trước khi test với code coverage , hãy đảm bảo đã import filterByTerm trong test /filterByTerm.spec.js :

const filterByTerm = require("../src/filterByTerm");
// ...

Save và test lại với coverage

npm test -- --coverage

Đây là những gì bạn thấy:

 PASS  __tests__/filterByTerm.spec.js
  Filter function
    ✓ it should filter by a search term (link) (3ms)
    ✓ it should filter by a search term (uRl) (1ms)
    ✓ it should throw when searchTerm is empty string (2ms)
-----------------|----------|----------|----------|----------|-------------------|
File             |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------------|----------|----------|----------|----------|-------------------|
All files        |     87.5 |       75 |      100 |      100 |                   |
 filterByTerm.js |     87.5 |       75 |      100 |      100 |                 3 |
-----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total

Một bản tóm tắt của coverage cho chức năng của chúng ta. Như bạn có thể thấy dòng 3 không được bảo vệ. Cố gắng fix để được 100% bằng cách test statement mà tôi vừa thêm.

Nếu bạn muốn giữ phạm vi bảo hiểm mã luôn hoạt động, hãy cấu hình Jest trong package.json như vậy:

  "scripts": {
    "test": "jest"
  },
  "jest": {
    "collectCoverage": true
  },

Bạn cũng có thể gắn flag cho file test:

  "scripts": {
    "test": "jest --coverage"
  },

Nếu bạn muốn dạng HTML

  "scripts": {
    "test": "jest"
  },
  "jest": {
    "collectCoverage": true,
    "coverageReporters": ["html"]
  },

Bây giờ mỗi khi bạn test npm, bạn có thể truy cập vào một thư mục mới có tên là  coverage  trong thư mục dự án của bạn: get-started-with-jest / coverage / . Trong thư mục đó, bạn sẽ tìm thấy một loạt các tệp trong đó /coverage/index.html là một bản tóm tắt HTML hoàn chỉnh về coverage :

Có bảo hiểm

Nếu bạn nhấp vào tên hàm, bạn cũng sẽ thấy đoạn code fail

Có bảo hiểm

Với coverage . Bạn sẽ có một bảng báo cáo chi tiết

Kết luận

Testing là một chủ đề lớn và hấp dẫn . Có nhiều loại testing và nhiều thư viện để testing. Trong hướng dẫn Jest này, bạn đã học cách config Jest để tạo báo cáo coverage , cách tổ chức và viết một Unit Test đơn giản.

Tham khảo thêm về test tại đây : Test ứng dụng REACT với PUPPETEER VÀ JEST


Like it? Share with your friends!

1071
1.7k shares, 1071 points

What's Your Reaction?

hate hate
334
hate
confused confused
2331
confused
fail fail
1332
fail
fun fun
999
fun
geeky geeky
666
geeky
love love
2998
love
lol lol
3331
lol
omg omg
2331
omg
win win
1335
win

0 Comments